Wie kann ich den Rückgabewert eines Prozesses negieren?

101

Ich bin auf der Suche nach einem einfachen, aber plattformübergreifende negate -Prozess , dass der Wert ein Prozess kehrt negiert. Es sollte 0 einem bestimmten Wert zuordnen! = 0 und einen beliebigen Wert! = 0 bis 0, dh der folgende Befehl sollte "yes, noneististingpath existiert nicht" zurückgeben:

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

Das ! - Operator ist großartig, aber leider nicht Shell-unabhängig.

Sec
quelle
Hatten Sie aus Neugier ein bestimmtes System im Sinn, das standardmäßig keine Bash enthält? Ich gehe davon aus, dass Sie mit "plattformübergreifend" nur * nix-basiert gemeint haben, da Sie eine Antwort akzeptiert haben, die nur auf einem * nix-System funktionieren würde.
Parthian Shot

Antworten:

110

Zuvor wurde die Antwort mit dem jetzt ersten Abschnitt als letztem Abschnitt präsentiert.

Die POSIX-Shell enthält einen !Operator

Beim Stöbern in der Shell-Spezifikation für andere Probleme habe ich kürzlich (September 2015) festgestellt, dass die POSIX-Shell einen !Operator unterstützt . Beispielsweise wird es als reserviertes Wort aufgeführt und kann am Anfang einer Pipeline angezeigt werden - wobei ein einfacher Befehl ein Sonderfall von 'Pipeline' ist. Es kann daher auch in ifAnweisungen und / whileoder untilSchleifen verwendet werden - in POSIX-kompatiblen Shells. Folglich ist es trotz meiner Vorbehalte wahrscheinlich weiter verbreitet als ich 2008 festgestellt habe. Eine kurze Überprüfung von POSIX 2004 und SUS / POSIX 1997 zeigt, dass !es in beiden Versionen vorhanden war.

Beachten Sie, dass der !Operator am Anfang der Pipeline erscheinen muss und den Statuscode der gesamten Pipeline negiert (dh den letzten Befehl). Hier sind einige Beispiele.

# 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

Tragbare Antwort - funktioniert mit antiken Muscheln

In einem Bourne-Skript (Korn, POSIX, Bash) verwende ich:

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

Dies ist so portabel wie es nur geht. Der 'Befehl und die Argumente' können eine Pipeline oder eine andere zusammengesetzte Folge von Befehlen sein.

Ein notBefehl

Das '!' Der in Ihre Shell integrierte oder von den Betriebssystemen bereitgestellte Operator ist nicht allgemein verfügbar. Es ist jedoch nicht besonders schwer zu schreiben - der folgende Code stammt mindestens aus dem Jahr 1991 (obwohl ich glaube, dass ich vor einiger Zeit eine frühere Version geschrieben habe). Ich verwende dies jedoch nicht in meinen Skripten, da es nicht zuverlässig verfügbar ist.

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

Dies gibt 'Erfolg' zurück, das Gegenteil von Fehler, wenn der Befehl nicht ausgeführt werden kann. Wir können darüber diskutieren, ob die Option "Nichts erfolgreich machen" richtig war. Vielleicht sollte es einen Fehler melden, wenn es nicht aufgefordert wird, etwas zu tun. Der Code in ' "stderr.h"' bietet einfache Funktionen zur Fehlerberichterstattung - ich verwende ihn überall. Quellcode auf Anfrage - siehe meine Profilseite, um mich zu kontaktieren.

Jonathan Leffler
quelle
+1 für 'Befehl und Argumente' kann eine Pipeline oder eine andere zusammengesetzte Folge von Befehlen sein. Andere Lösungen funktionieren nicht mit Pipeline
HVNSweeting
3
Bash-Umgebungsvariablen müssen dem !Operator folgen . Das hat bei mir funktioniert:! MY_ENV=value my_command
Daniel Böhmer
1
Die Negation funktioniert für die gesamte Pipe. Sie können dies also nicht tun, ldd foo.exe | ! grep badlibaber Sie können es tun, ! ldd foo.exe | grep badlibwenn der Exit-Status 0 sein soll, wenn badlib nicht in foo.exe gefunden wird. Semantisch möchten Sie den grepStatus invertieren, aber das Invertieren der gesamten Pipe führt zum gleichen Ergebnis.
Mark Lakata
@MarkLakata: Du hat Recht: sehen Sie die POSIX - Shell - Syntax und die damit verbundene Abschnitte (gehen Sie zu POSIX für die beste Look'n'Feel). Allerdings ist es möglich , zu verwenden ldd foo.exe | { ! grep badlib; }(obwohl es ein Ärgernis, vor allem des Semikolon, man kann eine vollständige Unterschale mit nutzen könnte (und )ohne das Semikolon). OTOH, das Ziel ist es, den Exit-Status der Pipeline zu invertieren, und das ist der Exit-Status des letzten Befehls (außer manchmal in Bash).
Jonathan Leffler
3
Entsprechend dieser Antwort hat der !Befehl eine unerwünschte Interaktion mit set -e, dh er bewirkt, dass der Befehl mit einem Exit-Code von 0 oder ungleich Null erfolgreich ist, anstatt nur mit einem Exit-Code ungleich Null erfolgreich zu sein. Gibt es eine präzise Möglichkeit, dies nur mit einem Exit-Code ungleich Null zum Erfolg zu führen ?
Elliott Slaughter
40

Verwenden Sie in Bash die! Operator vor dem Befehl. Zum Beispiel:

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

Du könntest es versuchen:

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

oder nur:

! ls nonexistingpath
Eduard Wirch
quelle
5
!Kann leider nicht verwendet werden git bisect run, oder zumindest weiß ich nicht wie.
Attila O.
1
Sie können jederzeit Inhalte in ein Shell-Skript einfügen. Git Bisect Run sieht das !in diesem Fall nicht.
Werkzeugschmiede
10

Wenn es irgendwie passiert, dass Sie Bash nicht als Shell haben (z. B. Git-Skripte oder Puppet Exec-Tests), können Sie Folgendes ausführen:

echo '! ls notexisting' | bash

-> Retcode: 0

echo '! ls /' | bash

-> Retcode: 1

Chris Suszyński
quelle
1
Um hier einen weiteren Breadcrumb einzufügen, ist dies für Zeilen im Skriptabschnitt von travis-ci erforderlich.
Kgraney
Dies war auch für BitBucket-Pipelines erforderlich.
KumZ
3
! ls nonexistingpath && echo "yes, nonexistingpath doesn't exist."

oder

ls nonexistingpath || echo "yes, nonexistingpath doesn't exist."
Robert Gamble
quelle
Ich habe kopiert, ob ich von der ursprünglichen Frage dachte, es sei Teil einer Pipeline, ich bin manchmal etwas langsam;)
Robert Gamble
Diese beiden Anweisungen sind fast gleichwertig, ABER der verbleibende Rückgabewert $?ist unterschiedlich. Der erste könnte gehen, $?=1wenn das nonexistingpathtatsächlich existiert. Der zweite geht immer $?=0. Wenn Sie dies in einem Makefile verwenden und sich auf den Rückgabewert verlassen müssen, müssen Sie vorsichtig sein.
Mark Lakata
1

Hinweis: Manchmal werden Sie sehen !(command || other command).
Hier ! ls nonexistingpath && echo "yes, nonexistingpath doesn't exist."ist genug.
Keine Unterschale erforderlich.

Git 2.22 (Q2 2019) veranschaulicht diese bessere Form mit:

Commit 74ec8cf , Commit 3fae7ad , Commit 0e67c32 , Commit 07353d9 , Commit 3bc2702 , Commit 8c3b9f7 , Commit 80a539a , Commit c5c39f4 (13. März 2019) von SZEDER Gábor ( szeder) .
Siehe Commit 99e37c2 , Commit 9f82b2a , Commit 900721e (13. März 2019) von Johannes Schindelin ( dscho) .
(Zusammengeführt von Junio ​​C Hamano - gitster- in Commit 579b75a , 25. April 2019)

t9811-git-p4-label-import: Pipeline-Negation korrigieren

In t9811-git-p4-label-import.sh' tag that cannot be exported' läuft der Test ' ':

!(p4 labels | grep GIT_TAG_ON_A_BRANCH)

um zu überprüfen, ob die angegebene Zeichenfolge nicht mit ' p4 labels' gedruckt wird .
Dies ist problematisch, weil laut POSIX :

"Wenn die Pipeline mit dem reservierten Wort beginnt !und command1ein Subshell-Befehl ist, muss die Anwendung sicherstellen, dass der (Operator am Anfang von durch ein oder mehrere Zeichen command1von dem getrennt ist . Das Verhalten des reservierten Wortes, auf das der Operator unmittelbar folgt , ist nicht spezifiziert. ""!<blank>
!(

Während die meisten gängigen Shells dies immer noch !als "Negieren des Exit-Codes des letzten Befehls in der Pipeline" mksh/lkshinterpretieren, tun Sie dies nicht und interpretieren Sie es stattdessen als negatives Dateinamenmuster.
Infolgedessen versuchen sie, einen Befehl auszuführen, der aus den Pfadnamen im aktuellen Verzeichnis besteht (es enthält ein einzelnes Verzeichnis mit dem Namen ' main'), was den Test natürlich nicht besteht.

Wir könnten es einfach beheben, indem wir ein Leerzeichen zwischen ' !' und ' (' einfügen. Stattdessen können wir es beheben, indem wir die unnötige Unterschale entfernen. Insbesondere Commit 74ec8cf

VonC
quelle
1

Lösung ohne !, Ohne Unterschale, ohne Wenn und sollte zumindest in Bash funktionieren:

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
quelle