Jak mogę zanegować wartość zwracaną przez proces?

103

Szukam prostego, ale wieloplatformowego procesu negacji, który neguje wartość zwracaną przez proces. Powinien odwzorować 0 na jakąś wartość! = 0 i dowolną wartość! = 0 na 0, tj. Poniższe polecenie powinno zwrócić „tak, nieistniejąca ścieżka nie istnieje”:

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

The! - operator jest świetny, ale niestety nie jest niezależny od powłoki.

secr
źródło
Czy z ciekawości miałeś na myśli konkretny system, który domyślnie nie zawiera basha? Zakładam, że przez „wieloplatformowy” miałeś na myśli tylko oparty na * nix, ponieważ zaakceptowałeś odpowiedź, która działałaby tylko w systemie * nix.
Parthian Shot

Odpowiedzi:

115

Wcześniej odpowiedź była przedstawiana jako ostatnia sekcja, która jest teraz pierwszą sekcją.

POSIX Shell zawiera !operator

Przeglądając specyfikację powłoki pod kątem innych problemów, niedawno (wrzesień 2015) zauważyłem, że powłoka POSIX obsługuje !operatora. Na przykład jest wymieniony jako słowo zastrzeżone i może pojawić się na początku potoku - gdzie proste polecenie jest specjalnym przypadkiem „potok”. Dlatego może być używany w ifinstrukcjach i / whilelub untilpętlach - w powłokach zgodnych z POSIX. W konsekwencji, pomimo moich zastrzeżeń, jest prawdopodobnie szerzej dostępny, niż zdawałem sobie sprawę w 2008 roku. Szybkie sprawdzenie POSIX 2004 i SUS / POSIX 1997 pokazuje, że !był obecny w obu tych wersjach.

Zauważ, że !operator musi pojawić się na początku potoku i zanegować kod statusu całego potoku (tj. Ostatnie polecenie). Oto kilka przykładów.

# 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

Przenośna odpowiedź - działa z antycznymi muszlami

W skrypcie Bourne'a (Korn, POSIX, Bash) używam:

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

To jest tak przenośne, jak to tylko możliwe. „Polecenie i argumenty” może być potokiem lub inną złożoną sekwencją poleceń.

notkomenda

Znak '!' Operator, niezależnie od tego, czy jest wbudowany w twoją powłokę, czy dostarczany przez O / S, nie jest powszechnie dostępny. Nie jest jednak strasznie trudno napisać - poniższy kod pochodzi przynajmniej z 1991 roku (choć myślę, że poprzednią wersję napisałem nawet wcześniej). Nie używam tego jednak w moich skryptach, ponieważ nie jest to niezawodnie dostępne.

/*
@(#)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);
}

Zwraca „sukces”, przeciwieństwo niepowodzenia, gdy nie wykonuje polecenia. Możemy debatować, czy opcja „nic nie rób z powodzeniem” była słuszna; może powinien zgłosić błąd, gdy nie jest proszony o nic. Kod w „ "stderr.h"” zapewnia proste narzędzia do raportowania błędów - używam go wszędzie. Kod źródłowy na życzenie - zobacz moją stronę profilu, aby się ze mną skontaktować.

Jonathan Leffler
źródło
+1 dla „Polecenie i argumenty” może być potokiem lub inną złożoną sekwencją poleceń. Inne rozwiązania nie działają z rurociągiem
HVNSweeting
3
Zmienne środowiskowe Bash muszą występować po !operatorze. To zadziałało dla mnie:! MY_ENV=value my_command
Daniel Böhmer
1
Negacja działa na całym potoku, więc nie możesz tego zrobić, ldd foo.exe | ! grep badlibale możesz to zrobić, ! ldd foo.exe | grep badlibjeśli chcesz, aby kod zakończenia wynosił 0, jeśli badlib nie zostanie znaleziony w foo.exe. Semantycznie chcesz odwrócić grepstan, ale odwrócenie całej potoku daje ten sam wynik.
Mark Lakata
@MarkLakata: Masz rację: zobacz składnię powłoki POSIX i powiązane sekcje (przejdź do POSIX, aby uzyskać najlepszy wygląd). Jednak jest to możliwe do wykorzystania ldd foo.exe | { ! grep badlib; }(choć jest to uciążliwe, zwłaszcza średnik; można użyć pełnej sub-shell z (i )bez średnika). OTOH, obiekt ma odwrócić status wyjścia potoku, a to jest status wyjścia ostatniego polecenia (z wyjątkiem czasami Bash).
Jonathan Leffler
3
Według tej odpowiedzi! komenda ma niepożądanej interakcji z set -e, czyli powoduje, że komenda do osiągnięcia sukcesu z kodem 0 lub niezerowe wyjścia, zamiast kolejny tylko z niezerowym kodem wyjścia. Czy istnieje zwięzły sposób, aby odnieść sukces tylko z niezerowym kodem zakończenia?
Elliott Slaughter
40

W Bash użyj! operator przed poleceniem. Na przykład:

! ls nonexistingpath && echo "yes, nonexistingpath doesn't exist"
Jay Conrod
źródło
17

Możesz spróbować:

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

Lub tylko:

! ls nonexistingpath
Eduard Wirch
źródło
5
Niestety !nie można z nim korzystać git bisect run, a przynajmniej nie wiem jak.
Attila O.,
1
Zawsze możesz umieścić coś w skrypcie powłoki; git bisect run nie widzi !w tym przypadku.
toolforger
10

Jeśli w jakiś sposób zdarzy się, że nie masz Bash jako powłoki (np. Skrypty git lub testy Puppet exec), możesz uruchomić:

echo '! ls notexisting' | bash

-> retcode: 0

echo '! ls /' | bash

-> retcode: 1

Chris Suszyński
źródło
1
Aby umieścić tutaj kolejny okruch, jest to konieczne w przypadku wierszy w sekcji skryptu travis-ci.
kgraney
Było to również konieczne w przypadku potoków BitBucket.
KumZ
3
! ls nonexistingpath && echo "yes, nonexistingpath doesn't exist."

lub

ls nonexistingpath || echo "yes, nonexistingpath doesn't exist."
Robert Gamble
źródło
Skopiowałem, jeśli z pierwotnego pytania myślałem, że to część potoku, czasami jestem trochę powolny;)
Robert Gamble
Te dwie instrukcje są prawie równoważne, ALE wartość zwracana pozostawiona w $?będzie inna. Pierwszy może odejść, $?=1jeśli nonexistingpathrzeczywiście istnieje. Drugi zawsze odchodzi $?=0. Jeśli używasz tego w pliku Makefile i musisz polegać na wartości zwracanej, musisz być ostrożny.
Mark Lakata
1

Uwaga: czasami zobaczysz !(command || other command).
Tutaj ! ls nonexistingpath && echo "yes, nonexistingpath doesn't exist."wystarczy.
Nie ma potrzeby stosowania podpowłoki.

Git 2.22 (Q2 2019) ilustruje tę lepszą formę z:

Zatwierdzić 74ec8cf , zatwierdzić 3fae7ad , zatwierdzić 0e67c32 , zatwierdzić 07353d9 , zatwierdzić 3bc2702 , zatwierdzić 8c3b9f7 , zatwierdzić 80a539a , zatwierdzić c5c39f4 (13 marca 2019) przez SZEDER Gábor ( szeder) .
Zobacz commit 99e37c2 , commit 9f82b2a , commit 900721e (13 marca 2019) autorstwa Johannes Schindelin ( dscho) .
(Scalone przez Junio ​​C Hamano - gitster- w zobowiązaniu 579b75a , 25 kwietnia 2019 r.)

t9811-git-p4-label-import: naprawiono negację potoku

W „ t9811-git-p4-label-import.sh” test „ tag that cannot be exported” działa:

!(p4 labels | grep GIT_TAG_ON_A_BRANCH)

aby sprawdzić, czy dany ciąg nie jest drukowany przez „ p4 labels”.
Jest to problematyczne, ponieważ zgodnie z POSIX :

„Jeśli potok zaczyna się od słowa zastrzeżonego !i command1jest poleceniem podpowłoki, aplikacja zapewnia, że (operator na początku command1jest oddzielony od !jednego lub więcej <blank>znaków.
Zachowanie słowa zastrzeżonego, !po którym bezpośrednio następuje (operator, jest nieokreślone. "

Podczas gdy większość popularnych powłok nadal interpretuje to ' !' jako „negację kodu wyjścia ostatniego polecenia w potoku”, „ mksh/lksh” nie i interpretuje to jako negatywny wzorzec nazwy pliku.
W rezultacie próbują uruchomić polecenie składające się z nazw ścieżek w bieżącym katalogu (zawiera pojedynczy katalog o nazwie „ main”), co oczywiście kończy się niepowodzeniem.

Moglibyśmy to naprawić, dodając spację między znakami „ !” i „ (”, ale zamiast tego naprawmy to, usuwając niepotrzebną podpowłokę. W szczególności Commit 74ec8cf

VonC
źródło
1

Rozwiązanie bez!, Bez podpowłoki, bez if i powinno działać przynajmniej w 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
źródło