Bagaimana saya bisa meniadakan nilai balik dari suatu proses?

103

Saya mencari sederhana, tapi cross-platform meniadakan -process yang meniadakan nilai proses pengembalian. Ini harus memetakan 0 ke beberapa nilai! = 0 dan nilai apa pun! = 0 ke 0, yaitu perintah berikut harus mengembalikan "ya, tidak ada jalur yang ada":

 ls nonexistingpath | negate && echo "yes, nonexistingpath doesn't exist."

The! - Operatornya bagus tapi sayangnya tidak bergantung pada shell.

secr
sumber
Karena penasaran, apakah Anda memiliki sistem khusus yang tidak menyertakan bash secara default? Saya berasumsi dengan "lintas-platform" yang Anda maksudkan adalah berbasis * nix, karena Anda menerima jawaban yang hanya akan bekerja pada sistem * nix.
Parthian Shot

Jawaban:

115

Sebelumnya, jawabannya disajikan dengan apa yang sekarang menjadi bagian pertama sebagai bagian terakhir.

POSIX Shell menyertakan !operator

Mengaduk-aduk spesifikasi shell untuk masalah lain, saya baru-baru ini (September 2015) memperhatikan bahwa shell POSIX mendukung !operator. Misalnya, ini terdaftar sebagai kata yang dipesan dan dapat muncul di awal pipeline - di mana perintah sederhana adalah kasus khusus 'pipeline'. Oleh karena itu, dapat digunakan dalam ifpernyataan dan whileatau untilloop juga - dalam shell yang sesuai dengan POSIX. Akibatnya, meskipun saya ragu, ini mungkin lebih banyak tersedia daripada yang saya sadari pada tahun 2008. Pemeriksaan cepat dari POSIX 2004 dan SUS / POSIX 1997 menunjukkan bahwa !ada di kedua versi tersebut.

Perhatikan bahwa !operator harus muncul di awal pipeline dan meniadakan kode status dari seluruh pipeline (yaitu perintah terakhir ). Berikut ini beberapa contohnya.

# Simple commands, pipes, and redirects work fine.
$ ! some-command succeed; echo $?
1
$ ! some-command fail | some-other-command fail; echo $?
0
$ ! some-command < succeed.txt; echo $?
1

# Environment variables also work, but must come after the !.
$ ! RESULT=fail some-command; echo $?
0

# A more complex example.
$ if ! some-command < input.txt | grep Success > /dev/null; then echo 'Failure!'; recover-command; mv input.txt input-failed.txt; fi
Failure!
$ ls *.txt
input-failed.txt

Jawaban portabel - bekerja dengan cangkang antik

Dalam skrip Bourne (Korn, POSIX, Bash), saya menggunakan:

if ...command and arguments...
then : it succeeded
else : it failed
fi

Ini portabel. 'Perintah dan argumen' dapat berupa pipa atau rangkaian perintah gabungan lainnya.

Sebuah notperintah

'!' operator, apakah built-in ke shell Anda atau disediakan oleh o / s, tidak tersedia secara universal. Tidak terlalu sulit untuk menulis, meskipun - kode di bawah ini berasal dari setidaknya tahun 1991 (meskipun saya pikir saya menulis versi sebelumnya lebih lama lagi). Saya tidak cenderung menggunakan ini dalam skrip saya, karena tidak tersedia secara andal.

/*
@(#)File:           $RCSfile: not.c,v $
@(#)Version:        $Revision: 4.2 $
@(#)Last changed:   $Date: 2005/06/22 19:44:07 $
@(#)Purpose:        Invert success/failure status of command
@(#)Author:         J Leffler
@(#)Copyright:      (C) JLSS 1991,1997,2005
*/

#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include "stderr.h"

#ifndef lint
static const char sccs[] = "@(#)$Id: not.c,v 4.2 2005/06/22 19:44:07 jleffler Exp $";
#endif

int main(int argc, char **argv)
{
    int             pid;
    int             corpse;
    int             status;

    err_setarg0(argv[0]);

    if (argc <= 1)
    {
            /* Nothing to execute. Nothing executed successfully. */
            /* Inverted exit condition is non-zero */
            exit(1);
    }

    if ((pid = fork()) < 0)
            err_syserr("failed to fork\n");

    if (pid == 0)
    {
            /* Child: execute command using PATH etc. */
            execvp(argv[1], &argv[1]);
            err_syserr("failed to execute command %s\n", argv[1]);
            /* NOTREACHED */
    }

    /* Parent */
    while ((corpse = wait(&status)) > 0)
    {
            if (corpse == pid)
            {
                    /* Status contains exit status of child. */
                    /* If exit status of child is zero, it succeeded, and we should
                       exit with a non-zero status */
                    /* If exit status of child is non-zero, if failed and we should
                       exit with zero status */
                    exit(status == 0);
                    /* NOTREACHED */
            }
    }

    /* Failed to receive notification of child's death -- assume it failed */
    return (0);
}

Ini mengembalikan 'sukses', kebalikan dari kegagalan, ketika gagal menjalankan perintah. Kita dapat memperdebatkan apakah opsi 'tidak berhasil' itu benar; mungkin itu harus melaporkan kesalahan ketika tidak diminta untuk melakukan apapun. Kode dalam ' "stderr.h"' menyediakan fasilitas pelaporan kesalahan sederhana - saya menggunakannya di mana-mana. Kode sumber atas permintaan - lihat halaman profil saya untuk menghubungi saya.

Jonathan Leffler
sumber
+1 untuk 'Perintah dan argumen' dapat berupa pipa atau rangkaian perintah gabungan lainnya. Solusi lain tidak berfungsi dengan pipa
HVNSweeting
3
Variabel lingkungan Bash harus mengikuti !operator. Ini berhasil bagi saya:! MY_ENV=value my_command
Daniel Böhmer
1
Negasi berfungsi di seluruh pipa, jadi Anda tidak dapat melakukannya ldd foo.exe | ! grep badlibtetapi Anda dapat melakukannya ! ldd foo.exe | grep badlibjika Anda ingin status keluar menjadi 0 jika badlib tidak ditemukan di foo.exe. Secara semantik Anda ingin membalik grepstatus, tetapi membalik seluruh pipa akan memberikan hasil yang sama.
Mark Lakata
@ MarkLakata: Anda benar: lihat sintaks shell POSIX dan bagian terkait (buka POSIX untuk mendapatkan tampilan terbaik). Namun, ini layak untuk digunakan ldd foo.exe | { ! grep badlib; }(meskipun mengganggu, terutama titik koma; Anda dapat menggunakan sub-shell lengkap dengan (dan )dan tanpa titik koma). OTOH, objeknya adalah untuk membalik status keluar dari pipa, dan itu adalah status keluar dari perintah terakhir (kecuali di Bash, kadang-kadang).
Jonathan Leffler
3
Menurut jawaban ini , !perintah memiliki interaksi yang tidak diinginkan set -e, yaitu menyebabkan perintah berhasil dengan kode keluar 0 atau bukan nol, alih-alih hanya berhasil dengan kode keluar bukan nol. Apakah ada cara ringkas untuk membuatnya berhasil hanya dengan kode keluar bukan nol?
Pembantaian Elliott
40

Di Bash, gunakan! operator sebelum perintah. Misalnya:

! ls nonexistingpath && echo "yes, nonexistingpath doesn't exist"
Jay Conrod
sumber
17

Kamu bisa mencoba:

ls nonexistingpath || echo "yes, nonexistingpath doesn't exist."

atau hanya:

! ls nonexistingpath
Eduard Wirch
sumber
5
Sayangnya !tidak dapat digunakan dengan git bisect run, atau setidaknya saya tidak tahu caranya.
Attila O.
1
Anda selalu dapat memasukkan sesuatu ke dalam skrip shell; git bisect run tidak melihat !kasus tersebut.
pemalsu alat
10

Jika entah bagaimana terjadi bahwa Anda tidak memiliki Bash sebagai shell Anda (misalnya: skrip git, atau tes eksekutif boneka), Anda dapat menjalankan:

echo '! ls notexisting' | bash

-> kode ulang: 0

echo '! ls /' | bash

-> kode ulang: 1

Chris Suszyński
sumber
1
Untuk meletakkan remah roti lain di sini, ini diperlukan untuk baris di bagian skrip travis-ci.
kgraney
Ini juga diperlukan untuk pipeline BitBucket.
KumZ
3
! ls nonexistingpath && echo "yes, nonexistingpath doesn't exist."

atau

ls nonexistingpath || echo "yes, nonexistingpath doesn't exist."
Robert Gamble
sumber
Saya menyalin jika dari pertanyaan awal, saya pikir itu adalah bagian dari pipa, kadang-kadang saya agak lambat;)
Robert Gamble
Kedua pernyataan ini hampir sama, TAPI nilai pengembalian yang tersisa $?akan berbeda. Yang pertama mungkin pergi $?=1jika nonexistingpathmemang benar ada. Yang kedua selalu pergi $?=0. Jika Anda menggunakan ini dalam Makefile dan perlu mengandalkan nilai kembalian, Anda harus berhati-hati.
Mark Lakata
1

Catatan: terkadang Anda akan melihat !(command || other command).
Ini ! ls nonexistingpath && echo "yes, nonexistingpath doesn't exist."sudah cukup.
Tidak perlu sub-shell.

Git 2.22 (Q2 2019) menggambarkan bentuk yang lebih baik dengan:

Komit 74ec8cf , komit 3fae7ad , komit 0e67c32 , komit 07353d9 , komit 3bc2702 , komit 8c3b9f7 , komit 80a539a , komit c5c39f4 (13 Mar 2019) oleh SZEDER Gábor ( szeder) .
Lihat commit 99e37c2 , commit 9f82b2a , commit 900721e (13 Mar 2019) oleh Johannes Schindelin ( dscho) .
(Digabung oleh Junio ​​C Hamano - gitster- di commit 579b75a , 25 Apr 2019)

t9811-git-p4-label-import: memperbaiki negasi pipa

Dalam ' t9811-git-p4-label-import.sh', pengujian ' tag that cannot be exported' berjalan:

!(p4 labels | grep GIT_TAG_ON_A_BRANCH)

untuk memeriksa bahwa string yang diberikan tidak dicetak oleh ' p4 labels'.
Ini bermasalah, karena menurut POSIX :

"Jika pipa dimulai dengan kata yang dipesan !dan command1merupakan perintah subkulit, aplikasi harus memastikan bahwa (operator di awal command1dipisahkan !oleh satu atau lebih <blank>karakter.
Perilaku dari kata yang dicadangkan !segera diikuti oleh (operator tidak ditentukan. "

Sementara kebanyakan cangkang masih menafsirkan ini '! ' ini sebagai "meniadakan kode keluar dari perintah terakhir dalam pipa", ' mksh/lksh' jangan dan interpretasikan sebagai pola nama file negatif.
Akibatnya, mereka mencoba menjalankan perintah yang terdiri dari nama jalur di direktori saat ini (berisi satu direktori bernama 'main '), yang, tentu saja, gagal dalam pengujian.

Kita dapat memperbaikinya hanya dengan menambahkan spasi antara ' !' dan ' (', tetapi mari kita perbaiki dengan menghapus subkulit yang tidak perlu. Secara khusus, Komit 74ec8cf

VonC
sumber
1

Solusi tanpa!, Tanpa subkulit, tanpa if, dan harus berfungsi setidaknya di bash:

ls nonexistingpath; test $? -eq 2 && echo "yes, nonexistingpath doesn't exist."

# alternatively without error message and handling of any error code > 0
ls nonexistingpath 2>/dev/null; test $? -gt 0 && echo "yes, nonexistingpath doesn't exist."
4irmann
sumber