
Ich versuche zu verstehen, wie der Beendigungsstatus übermittelt wird, wenn eine Pipe verwendet wird. Angenommen, ich verwende diese, which
um ein nicht vorhandenes Programm zu finden:
which lss
echo $?
1
Da which
ich es nicht finden konnte, lss
habe ich den Exit-Status 1. 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. Ich kann das nur so verstehen, dass PIPE möglicherweise auch einen Beendigungsstatus erzeugt. Ist das die richtige Art, es zu verstehen?
Antwort1
Der Beendigungsstatus einer Pipe ist der Beendigungsstatus des rechten Befehls. Der Beendigungsstatus des linken Befehls wird ignoriert.
(Beachten Sie, dass which lss | echo $?
dies nicht angezeigt wird. Sie würden ausführen, which lss | true; echo $?
um dies anzuzeigen. In which lss | echo $?
meldet echo $?
den Status des letzten Befehls vor dieser Pipeline.)
Der Grund für dieses Verhalten von Shells ist, dass es ein recht häufiges Szenario gibt, in dem ein Fehler auf der linken Seite ignoriert werden sollte. Wenn die rechte Seite beendet wird (oder allgemeiner 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, ist es in Ordnung, wenn sie anhält.
Wenn die linke Seite jedoch aus einem anderen Grund als SIGPIPE abstürzt oder wenn die Aufgabe der linken Seite nicht ausschließlich darin bestand, Daten auf der Standardausgabe zu erzeugen, dann ist ein Fehler auf der linken Seite ein echter Fehler, der gemeldet werden sollte.
In einfachem SH besteht die einzige Lösung darin, eine benannte Pipe zu verwenden.
set -e
mkfifo p
command1 >p & pid1=$!
command2 <p
wait $pid1
In ksh, bash und zsh können Sie die Shell anweisen, die Pipeline mit einem Status ungleich Null zu beenden, wenn eine beliebige Komponente der Pipeline mit einem Status ungleich Null beendet wird. Sie müssen die pipefail
Option festlegen:
- ksch:
set -o pipefail
- bash:
shopt -s pipefail
- zsh:
setopt pipefail
(odersetopt pipe_fail
)
In mksh, bash und zsh können Sie den Status jeder Komponente der Pipeline mit der Variable PIPESTATUS
(bash, mksh) oder pipestatus
(zsh) abrufen, die ein Array ist, das den Status aller Befehle in der letzten Pipeline enthält (eine Verallgemeinerung von $?
).
Antwort2
Der Beendigungsstatus der Pipeline ist der Beendigungsstatus des letzten Befehls in der Pipeline (es sei denn, die Shell-Option pipefail
ist in den Shells festgelegt, die sie unterstützen. In diesem Fall ist der Beendigungsstatus der des letzten Befehls in der Pipeline, der mit einem Status ungleich Null beendet wird).
Es ist nicht möglich, den Exit-Status einer Pipeline auszugebenvon inneneine Pipeline, da dieser Wert erst bekannt sein muss, wenn die Ausführung der Pipeline tatsächlich abgeschlossen ist.
Die Pipeline
which lss | echo $?
ist unsinnig, da echo
es seine Standardeingabe nicht liest (Pipelines werden verwendet, um Daten zwischen der Ausgabe eines Befehls und der Eingabe des nächsten zu übergeben). Es echo
würde auch nicht den Beendigungsstatus der Pipeline drucken, sondern den Beendigungsstatus des sofort ausgeführten BefehlsVordie Pipeline.
$ 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
Antwort3
Ja, PIPE erzeugt einen Exit-Status. Wenn Sie den Exit-Code des Befehls vor dem PIPE haben möchten, können Sie Folgendes verwenden:$PIPESTATUS
Entsprechenddiese Stack Overflow-Fragen und -Antworten, um den Beendigungsstatus eines Befehls auszudrucken, wenn Sie eine Pipe verwenden, haben Sie folgende Möglichkeiten:
your_command | echo $PIPESTATUS
Oder setzen Sie Pipefail mit dem Befehl set -o pipefail
(um es aufzuheben, führen Sie aus set +o pipefail
) und verwenden Sie $?
anstelle von $PIPESTATUS
.
your_command | echo $?
Diese Befehle funktionieren nur, nachdem Sie sie mindestens einmal ausgeführt haben. Das heißt, PIPESTATUS
sie funktionieren nur, wenn Sie sie nach dem Befehl mit der Pipe ausführen, wie folgt:
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]}
. Siehedieses U&L Q&Afür mehr Informationen.
Antwort4
Die Shell wertet die Kommandozeile aus, bevor die Pipeline ausgeführt wird. Dabei $?
wird der Wert des letzten Kommandos ausgewertet, das vor der Pipeline ausgeführt wurde. Ob echo
sie selbst vorher oder nachher gestartet wird, which
spielt dabei keine Rolle.
Wenn sich Ihre Frage speziell auf die Weitergabe des Beendigungsstatus bezieht, können Sie das Standardverhalten folgendermaßen untersuchen:
% false | true
% echo $?
0
% true | false
% echo $?
1
Wie Sie sehen, ist der Beendigungsstatus einer Pipeline der Beendigungsstatus ihres letzten Befehls.