Bash-Exit-Status, der mit PIPE verwendet wird

10

Ich versuche zu verstehen, wie der Ausgangsstatus kommuniziert wird, wenn eine Pipe verwendet wird. Angenommen, ich verwende which, um ein nicht vorhandenes Programm zu finden:

which lss
echo $?
1

Da ich whichnicht gefunden habe, habe lssich den Exit-Status 1 erhalten. Das ist in Ordnung. Wenn ich jedoch Folgendes versuche:

which lss | echo $?
0

Dies zeigt an, dass der zuletzt ausgeführte Befehl normal beendet wurde. Der einzige Weg, dies zu verstehen, ist, dass PIPE möglicherweise auch einen Exit-Status erzeugt. Ist das der richtige Weg, es zu verstehen?

sshekhar1980
quelle
2
Alle Komponenten einer Pipeline werden gleichzeitig ausgeführt . So wird in which lss | echo $?der echogestartet wird vor dem whichVerlassen der ; Die Shell, für die die Befehlszeile generiert echowird, kann nicht wissen, wie der Exit-Status whichspäter aussehen wird.
Charles Duffy
Sie laufen nicht gleichzeitig. Der gesamte Zweck der Pipe besteht darin, die Ausgabe eines Befehls in den anderen umzuleiten. Der letzte Befehl, den die Shell ausgeführt hat (dh der letzte in Ihrer Anweisung), gibt einen Rückkehrcode zurück.
Tim S.
3
@ TimS. Sie werden jedoch gleichzeitig ausgeführt. Auf diese Weise können Sie die ersten Ausgabebits abrufen, bevor der erste Befehl abgeschlossen ist, wenn es sich um einen sehr lang laufenden Befehl handelt.
Random832
Ich denke, ich habe über den Beginn der Ausführung nachgedacht - es gibt eindeutige Überschneidungen, aber die Prozesse beginnen nicht zusammen. Ich verstehe, was du meinst, mein Fehler. (Die Pipe leitet die Ausgabe um, nicht die Ausführung ...) Ich habe meine Kommentare verpfuscht, aber hoffentlich wissen Sie, was ich meine. :)
Tim S.
Ja, aber der Beginn der Ausführung spielt keine Rolle. Wichtig ist das Ende. Der letzte Befehl beginnt, bevor der erste endet
user371366

Antworten:

14

Der Exit-Status der Pipeline ist der Exit-Status des letzten Befehls in der Pipeline (es sei denn, die Shell-Option pipefailist in den Shells festgelegt, die ihn unterstützen. In diesem Fall ist der Exit-Status der des letzten Befehls in der Pipeline, der mit beendet wird ein Status ungleich Null).

Es ist nicht möglich, den Exit-Status einer Pipeline innerhalb einer Pipeline auszugeben , da es keine Möglichkeit gibt, den Wert zu ermitteln, bis die Ausführung der Pipeline tatsächlich abgeschlossen ist.

Die Pipeline

which lss | echo $?

ist unsinnig, da echodie Standardeingabe nicht gelesen wird (Pipelines werden verwendet, um Daten zwischen der Ausgabe eines Befehls und der Eingabe des nächsten zu übergeben). Das echowürde auch nicht den Exit-Status der Pipeline drucken, sondern den Exit-Status des Befehls, der unmittelbar vor der Pipeline ausgeführt wird.

$ false
$ which hello | echo $?
1
which: hello: Command not found.

$ true
$ which hello | echo $?
0
which: hello: Command not found.

Dies ist ein besseres Beispiel:

$ echo hello | read a
$ echo $?
0

$ echo nonexistent | { read filename && rm $filename; }
rm: nonexistent: No such file or directory
$ echo $?
1

Dies bedeutet, dass eine Pipeline auch verwendet werden kann mit if:

if gzip -dc file.gz | grep -q 'something'; then
  echo 'something was found'
fi
Kusalananda
quelle
10

Der Ausgangsstatus einer Pipe ist der Ausgangsstatus des rechten Befehls. Der Exit-Status des linken Befehls wird ignoriert.

(Beachten Sie, dass which lss | echo $?dies nicht angezeigt wird. Sie würden es ausführen which lss | true; echo $?, um dies anzuzeigen. In which lss | echo $?gibt echo $?den Status des letzten Befehls vor dieser Pipeline an.)

Der Grund, warum sich Shells so verhalten, ist, dass es ein ziemlich häufiges Szenario gibt, in dem ein Fehler auf der linken Seite ignoriert werden sollte. Wenn die rechte Seite beendet wird (oder im Allgemeinen ihre Standardeingabe schließt), während die linke Seite noch schreibt, empfängt die linke Seite ein SIGPIPE-Signal. In diesem Fall ist normalerweise nichts falsch: Die rechte Seite kümmert sich nicht um die Daten; Wenn die Rolle der linken Seite ausschließlich darin besteht, diese Daten zu erzeugen, kann sie gestoppt werden.

Wenn jedoch die linke Seite aus einem anderen Grund als SIGPIPE stirbt oder wenn die Aufgabe der linken Seite nicht nur darin bestand, Daten zur Standardausgabe zu erzeugen, ist ein Fehler auf der linken Seite ein echter Fehler sollte gemeldet werden.

Im Klartext besteht die einzige Lösung darin, ein benanntes Rohr zu verwenden.

set -e
mkfifo p
command1 >p & pid1=$!
command2 <p
wait $pid1

In ksh, bash und zsh können Sie die Shell anweisen, einen Pipeline-Exit mit einem Status ungleich Null durchzuführen, wenn eine Komponente der Pipeline mit einem Status ungleich Null beendet wird. Sie müssen die pipefailOption einstellen :

  • ksh: set -o pipefail
  • Bash: shopt -s pipefail
  • zsh: setopt pipefail(oder setopt pipe_fail)

In mksh, bash und zsh können Sie den Status jeder Komponente der Pipeline mithilfe der Variablen PIPESTATUS(bash, mksh) oder pipestatus(zsh) abrufen. Hierbei handelt es sich um ein Array, das den Status aller Befehle in der letzten Pipeline enthält (eine Verallgemeinerung von $?).

Gilles 'SO - hör auf böse zu sein'
quelle
Vielen Dank, Gilles. Ich war mir dieser Komplexität nicht bewusst. Das hat das für mich wirklich weiter geklärt.
sshekhar1980
4

Ja, PIPE erzeugt einen Exit-Status. Wenn Sie den Exit-Code des Befehls vor dem PIPE haben möchten, können Sie ihn verwenden$PIPESTATUS

Gemäß diesen Fragen und Antworten zum Stapelüberlauf können Sie Folgendes tun, um den Beendigungsstatus eines Befehls zu drucken, wenn Sie eine Pipe verwenden:

your_command | echo $PIPESTATUS

Oder setzen Sie Pipefail mit dem Befehl set -o pipefail(um es zu deaktivieren , tun Sie es set +o pipefail) und verwenden Sie $?stattdessen $PIPESTATUS.

your_command | echo $?

Diese Befehle funktionieren nur, nachdem Sie sie mindestens einmal ausgeführt haben. Das bedeutet, dass es PIPESTATUSnur funktioniert, wenn Sie es nach dem Befehl mit der Pipe wie folgt ausführen:

command_which_exit_10 | command_which_exit_1
echo "${PIPESTATUS[0]} ${PIPESTATUS[1]}"

Sie erhalten 10 für ${PIPESTATUS[0]}und 1 für ${PIPESTATUS[1]}. Weitere Informationen finden Sie in diesen U & L-Fragen und Antworten.

jeremy.Snidaro
quelle
2
Während PIPESTATUSIhr erstes Beispiel den Exit-Status der Befehle in der letzten Pipeline enthält, ist es nicht sinnvoller als das somecmd | echo $?, worauf Kusalananda in seiner Antwort eingegangen ist. false; true | echo $PIPESTATUSdruckt 1für den Exit-Status von false.
Ilkkachu
Upvote für PIPESTATUS und Pipefiail, aber das |exhoist wirklich verwirrend
eckes
0

Die Shell wertet die Befehlszeile aus, bevor die Pipeline ausgeführt wird. Dies $?gilt auch für den Wert des letzten Befehls, der vor der Pipeline ausgeführt wurde. Ob echoselbst vorher oder nachher gestartet whichwird, spielt keine Rolle.

Wenn Ihre Frage speziell die Weitergabe des Exit-Status betraf, können Sie das Standardverhalten folgendermaßen untersuchen:

% false | true
% echo $?
0

% true | false
% echo $?
1

Wie Sie sehen können, ist der Exit-Status einer Pipeline der Exit-Status ihres letzten Befehls.

alexis
quelle