weitergeleitet wurde. Ich habe zwei Prozesse foo
und bar
, verbunden mit einer Pipe:
$ foo | bar
bar
beendet immer 0; Ich interessiere mich für den Exit-Code von foo
. Gibt es eine Möglichkeit, darauf zuzugreifen?
Kommentare
Antwort
bash
und zsh
haben eine Array-Variable Dies enthält den Exit-Status jedes Elements (Befehls) der letzten von der Shell ausgeführten Pipeline.
Wenn Sie bash
verwenden, heißt das Array PIPESTATUS
(Groß- und Kleinschreibung ist wichtig!) und die Array-Anzeigen beginnen bei Null:
$ false | true $ echo "${PIPESTATUS[0]} ${PIPESTATUS[1]}" 1 0
Wenn Sie zsh
heißt das Array pipestatus
(case case!) und die Array-Indizes beginnen bei eins:
$ false | true $ echo "${pipestatus[1]} ${pipestatus[2]}" 1 0
Um sie innerhalb einer Funktion so zu kombinieren, dass die Werte nicht verloren gehen:
$ false | true $ retval_bash="${PIPESTATUS[0]}" retval_zsh="${pipestatus[1]}" retval_final=$? $ echo $retval_bash $retval_zsh $retval_final 1 0
Führen Sie die obigen Schritte in bash
oder zsh
und Sie erhalten die gleichen Ergebnisse. Es wird nur eines von retval_bash
und retval_zsh
festgelegt. Der andere wird leer sein. Dies würde es einer Funktion ermöglichen, mit return $retval_bash $retval_zsh
zu enden (beachten Sie das Fehlen von Anführungszeichen!).
Kommentare
- Und
pipestatus
in zsh. Leider haben andere Shells ‚ diese Funktion nicht. - Hinweis: Arrays in zsh beginnen nicht intuitiv bei Index 1, daher ‚ s
echo "$pipestatus[1]" "$pipestatus[2]"
. - Sie können die gesamte Pipeline folgendermaßen überprüfen:
if [ `echo "${PIPESTATUS[@]}" | tr -s ' ' + | bc` -ne 0 ]; then echo FAIL; fi
- @JanHudec: Vielleicht solltest du die ersten fünf Wörter meiner Antwort lesen. Bitte geben Sie auch an, wo die Frage eine reine POSIX-Antwort angefordert hat.
- @JanHudec: Sie wurde auch nicht mit POSIX markiert. Warum muss die Antwort wohl POSIX sein? Es wurde nicht angegeben, daher gab ich eine qualifizierte Antwort. An meiner Antwort ist nichts Falsches, und es gibt mehrere Antworten, um andere Fälle anzusprechen.
Antwort
Dort Es gibt drei gängige Methoden, um dies zu tun:
Pipefail
Der erste Weg besteht darin, die Option pipefail
(, zsh
oder bash
). Dies ist am einfachsten und setzt im Grunde genommen den Exit-Status $?
auf den Exit-Code des letzten Programms, das ungleich Null beendet wird (oder Null, wenn alle erfolgreich beendet wurden).
$ false | true; echo $? 0 $ set -o pipefail $ false | true; echo $? 1
$ PIPESTATUS
Bash hat auch eine Array-Variable namens $PIPESTATUS
($pipestatus
in zsh
), das den Exit-Status aller Programme in der letzten Pipeline enthält.
$ true | true; echo "${PIPESTATUS[@]}" 0 0 $ false | true; echo "${PIPESTATUS[@]}" 1 0 $ false | true; echo "${PIPESTATUS[0]}" 1 $ true | false; echo "${PIPESTATUS[@]}" 0 1
Sie können das dritte Befehlsbeispiel verwenden, um den spezifischen Wert in der Pipeline abzurufen, den Sie benötigen.
Separate Ausführungen
Dies ist die unhandlichste der Lösungen. Führen Sie jeden Befehl separat aus und erfassen Sie den Status
$ OUTPUT="$(echo foo)" $ STATUS_ECHO="$?" $ printf "%s" "$OUTPUT" | grep -iq "bar" $ STATUS_GREP="$?" $ echo "$STATUS_ECHO $STATUS_GREP" 0 1
Kommentare
- Verdammt! Ich wollte gerade über PIPESTATUS posten.
- Als Referenz werden in dieser SO-Frage verschiedene andere Techniken behandelt: stackoverflow.com/questions/1221833/…
- @Patrick Die Pipestatus-Lösung funktioniert mit Bash. Nur mehr Fragen, falls ich ein Ksh-Skript verwende. Denken Sie, wir können etwas Ähnliches wie Pipestatus finden? , (währenddessen sehe ich, dass der Pipestatus von ksh nicht unterstützt wird)
- @yael Ich ‚ verwende nicht
ksh
, Bei einem kurzen Blick auf die Manpage von ‚ wird ‚$PIPESTATUS
nicht unterstützt oder ähnliches. Es unterstützt jedoch die Optionpipefail
. - Ich habe mich für Pipefail entschieden, da ich hier den Status des fehlgeschlagenen Befehls abrufen kann:
LOG=$(failed_command | successful_command)
Antwort
Diese Lösung funktioniert ohne Verwendung von Bash-spezifischen Funktionen oder temporären Dateien . Bonus: Am Ende ist der Exit-Status tatsächlich ein Exit-Status und keine Zeichenfolge in einer Datei.
Situation:
someprog | filter
Sie Ich möchte den Exit-Status von someprog
und die Ausgabe von filter
.
Hier ist meine Lösung:
((((someprog; echo $? >&3) | filter >&4) 3>&1) | (read xs; exit $xs)) 4>&1
Das Ergebnis dieses Konstrukts ist stdout von filter
als stdout des Konstrukts und beendet den Status von someprog
als Exit-Status des Konstrukts.
Dieses Konstrukt funktioniert auch mit der einfachen Befehlsgruppierung {...}
anstelle von Unterschalen (...)
. Unterschalen haben einige Auswirkungen, unter anderem Leistungskosten, die wir hier nicht benötigen. Weitere Informationen finden Sie im Fine Bash-Handbuch: https://www.gnu.org/software/bash/manual/html_node/Command-Grouping.html
{ { { { someprog; echo $? >&3; } | filter >&4; } 3>&1; } | { read xs; exit $xs; } } 4>&1
Leider benötigt die Bash-Grammatik Leerzeichen und Semikolons für die geschweiften Klammern, damit das Konstrukt viel geräumiger wird.
Für den Rest dieses Textes werde ich die Subshell-Variante verwenden.
Beispiel someprog
und filter
:
someprog() { echo "line1" echo "line2" echo "line3" return 42 } filter() { while read line; do echo "filtered $line" done } ((((someprog; echo $? >&3) | filter >&4) 3>&1) | (read xs; exit $xs)) 4>&1 echo $?
Beispielausgabe:
filtered line1 filtered line2 filtered line3 42
Hinweis: Der untergeordnete Prozess erbt die geöffneten Dateideskriptoren vom übergeordneten Element. Das bedeutet, dass someprog
die offenen Dateideskriptoren 3 und 4 erbt. Wenn someprog
in den Dateideskriptor 3 schreibt, wird dies zum Exit-Status. Der tatsächliche Beendigungsstatus wird ignoriert, da read
nur einmal liest.
Wenn Sie befürchten, dass Ihre someprog
möglicherweise schreibt Um den Dateideskriptor 3 oder 4 zu verwenden, schließen Sie am besten die Dateideskriptoren, bevor Sie someprog
aufrufen.
(((((exec 3>&- 4>&-; someprog); echo $? >&3) | filter >&4) 3>&1) | (read xs; exit $xs)) 4>&1
Die exec 3>&- 4>&-
vor someprog
schließt den Dateideskriptor, bevor someprog
ausgeführt wird, also für someprog
Diese Dateideskriptoren existieren einfach nicht.
Es kann auch so geschrieben werden: someprog 3>&- 4>&-
Schrittweise Erklärung des Konstrukts:
( ( ( ( someprog; #part6 echo $? >&3 #part5 ) | filter >&4 #part4 ) 3>&1 #part3 ) | (read xs; exit $xs) #part2 ) 4>&1 #part1
Von unten nach oben:
- Mit dem Dateideskriptor wird eine Unterschale erstellt 4 auf stdout umgeleitet. Dies bedeutet, dass alles, was in der Unterschale in Dateideskriptor 4 gedruckt wird, als Standard des gesamten Konstrukts endet.
- Eine Pipe wird erstellt und die Befehle auf der linken Seite (
#part3
) und right (#part2
) werden ausgeführt.exit $xs
ist auch der letzte Befehl der Pipe. Dies bedeutet, dass die Zeichenfolge von stdin der Exit-Status des gesamten Konstrukts ist. - Mit Datei wird eine Subshell erstellt Deskriptor 3 auf stdout umgeleitet. Dies bedeutet, dass alles, was in dieser Unterschale in Dateideskriptor 3 gedruckt wird, in
#part2
endet und wiederum der Exit-Status des gesamten Konstrukts ist. - A. Pipe wird erstellt und die Befehle links (
#part5
und#part6
) und rechts (filter >&4
) ausgeführt werden. Die Ausgabe vonfilter
wird an den Dateideskriptor 4 umgeleitet. In#part1
wurde der Dateideskriptor 4 an stdout umgeleitet. Dies bedeutet, dass die Ausgabe vonfilter
die Standardausgabe des gesamten Konstrukts ist. - Der Beendigungsstatus von
#part6
wird gedruckt zu Dateideskriptor 3. In#part3
wurde Dateideskriptor 3 zu#part2
umgeleitet. Dies bedeutet, dass der Exit-Status von#part6
der endgültige Exit-Status für das gesamte Konstrukt ist. -
someprog
ist hingerichtet. Der Exit-Status wird in#part5
übernommen. Die Standardausgabe wird von der Pipe in#part4
übernommen und anfilter
weitergeleitet. Die Ausgabe vonfilter
erreicht wiederum stdout, wie in#part4
Kommentare h3 erläutert >
- Schön. Für die Funktion könnte ich nur
(read; exit $REPLY)
-
(exec 3>&- 4>&-; someprog)
vereinfachen, um someprog 3>&- 4>&-
.
- Diese Methode funktioniert auch ohne Unterschalen:
{ { { { someprog 3>&- 4>&-; echo $? >&3; } | filter >&4; } 3>&1; } | { read xs; exit $xs; }; } 4>&1
(read; exit $REPLY)
(exec 3>&- 4>&-; someprog)
vereinfachen, um someprog 3>&- 4>&-
. { { { { someprog 3>&- 4>&-; echo $? >&3; } | filter >&4; } 3>&1; } | { read xs; exit $xs; }; } 4>&1
Antwort
Obwohl dies nicht genau das ist, was Sie gefragt haben, können Sie
#!/bin/bash -o pipefail
verwenden, damit Ihre Pipes die letzte Rückgabe ungleich Null zurückgeben.
ist möglicherweise etwas weniger codiert
Bearbeiten: Beispiel
[root@localhost ~]# false | true [root@localhost ~]# echo $? 0 [root@localhost ~]# set -o pipefail [root@localhost ~]# false | true [root@localhost ~]# echo $? 1
Kommentare
-
set -o pipefail
im Skript sollte robuster sein, z falls jemand das Skript überbash foo.sh
ausführt. - Wie funktioniert das? Haben Sie ein Beispiel?
- Beachten Sie, dass
-o pipefail
nicht in POSIX enthalten ist. - Dies funktioniert in meinem BASH 3.2.25 ( 1) -freigabe. Am Anfang von / tmp / ff habe ich
#!/bin/bash -o pipefail
.Fehler ist:/bin/bash: line 0: /bin/bash: /tmp/ff: invalid option name
- @FelipeAlvarez: Einige Umgebungen (einschließlich Linux) analysieren ‚ keine Leerzeichen auf
#!
Zeilen jenseits der ersten, und so wird dies zu/bin/bash
-o pipefail
/tmp/ff
anstelle der erforderlichen/bin/bash
-o
pipefail
/tmp/ff
–getopt
(oder ähnliches) Parsen mitoptarg
, dem nächsten Element inARGV
, als Argument für-o
, daher schlägt es fehl. Wenn Sie einen Wrapper erstellen (z. B.bash-pf
, der geradeexec /bin/bash -o pipefail "$@"
ausgeführt hat, und diesen auf den Zeile, das würde funktionieren. Siehe auch: en.wikipedia.org/wiki/Shebang_%28Unix%29
Antwort
Wenn möglich, füge ich den Exit-Code von foo
in bar
. Wenn ich beispielsweise weiß, dass foo
niemals eine Zeile mit nur Ziffern erzeugt, kann ich einfach den Exit-Code anheften:
{ foo; echo "$?"; } | awk "!/[^0-9]/ {exit($0)} {…}"
Oder wenn ich weiß, dass die Ausgabe von foo
niemals eine Zeile mit nur :
{ foo; echo .; echo "$?"; } | awk "/^\.$/ {getline; exit($0)} {…}"
Dies ist immer möglich, wenn es eine Möglichkeit gibt, bar
um alle bis auf die letzte Zeile zu bearbeiten und die letzte Zeile als Exit-Code weiterzugeben.
Wenn bar
eine komplexe Pipeline ist, deren Ausgabe Sie Sie müssen keinen Teil davon umgehen, indem Sie den Exit-Code auf einen anderen Dateideskriptor drucken.
exit_codes=$({ { foo; echo foo:"$?" >&3; } | { bar >/dev/null; echo bar:"$?" >&3; } } 3>&1)
Danach $exit_codes
ist normalerweise foo:X bar:Y
, aber es könnte bar:Y foo:X
sein, wenn bar
beendet wird bevor Sie alle Eingaben lesen oder wenn Sie „Pech haben“. Ich denke, Schreibvorgänge in Pipes mit bis zu 512 Bytes sind auf allen Unices atomar, daher werden die Teile foo:$?
und bar:$?
nicht als gemischt Solange die Tag-Zeichenfolgen unter 507 Byte liegen.
Wenn Sie die Ausgabe von bar
erfassen müssen, wird es schwierig. Sie können die oben genannten Techniken durch Anordnen kombinieren Damit die Ausgabe von bar
niemals eine Zeile enthält, die wie eine Exit-Code-Anzeige aussieht, aber fummelig wird.
output=$(echo; { { foo; echo foo:"$?" >&3; } | { bar | sed "s/^/^/"; echo bar:"$?" >&3; } } 3>&1) nl=" " foo_exit_code=${output#*${nl}foo:}; foo_exit_code=${foo_exit_code%%$nl*} bar_exit_code=${output#*${nl}bar:}; bar_exit_code=${bar_exit_code%%$nl*} output=$(printf %s "$output" | sed -n "s/^\^//p")
Und natürlich gibt es die einfache Möglichkeit, eine temporäre Datei zum Speichern des Status zu verwenden. Einfach, aber nicht so einfach in der Produktion:
- Wenn mehrere Skripte gleichzeitig ausgeführt werden oder wenn dasselbe Skript diese Methode an mehreren Stellen verwendet, müssen Sie dies tun Stellen Sie sicher, dass sie unterschiedliche temporäre Dateinamen verwenden.
- Das sichere Erstellen einer temporären Datei in einem freigegebenen Verzeichnis ist schwierig. Oft ist
/tmp
der einzige Ort, an dem ein Skript sicher Dateien schreiben kann. Verwenden Siemktemp
, das nicht POSIX ist, aber heutzutage auf allen ernsthaften Unices verfügbar ist.
foo_ret_file=$(mktemp -t) { foo; echo "$?" >"$foo_ret_file"; } | bar bar_ret=$? foo_ret=$(cat "$foo_ret_file"; rm -f "$foo_ret_file")
Kommentare
- Bei Verwendung des Ansatzes für temporäre Dateien bevorzuge ich das Hinzufügen eines Traps für EXIT, der alle temporären Dateien entfernt Damit kein Müll übrig bleibt, auch wenn das Skript stirbt
Antwort
Ausgehend von der Pipeline:
foo | bar | baz
Hier ist eine allgemeine Lösung, bei der nur die POSIX-Shell und keine temporären Dateien verwendet werden:
exec 4>&1 error_statuses="`((foo || echo "0:$?" >&3) | (bar || echo "1:$?" >&3) | (baz || echo "2:$?" >&3)) 3>&1 >&4`" exec 4>&-
$error_statuses
enthält die Statuscodes aller fehlgeschlagenen Prozesse in zufälliger Reihenfolge mit Indizes, die angeben, welcher Befehl jeden Status ausgegeben hat.
# if "bar" failed, output its status: echo "$error_statuses" | grep "1:" | cut -d: -f2 # test if all commands succeeded: test -z "$error_statuses" # test if the last command succeeded: ! echo "$error_statuses" | grep "2:" >/dev/null
Beachten Sie die Anführungszeichen um $error_statuses
in meinen Tests. ohne sie kann grep
nicht differenzieren, da die Zeilenumbrüche zu Leerzeichen gezwungen werden.
Antwort
Wenn Sie das Paket moreutils installiert haben, können Sie das Dienstprogramm mispipe verwenden, das genau das tut, was Sie gefragt haben.
Antwort
Ich wollte also eine Antwort wie die von Lesmana einbringen, aber ich denke, meine ist vielleicht etwas einfacher und etwas vorteilhafter. Bourne-Shell-Lösung:
# You want to pipe command1 through command2: exec 4>&1 exitstatus=`{ { command1; printf $? 1>&3; } | command2 1>&4; } 3>&1` # $exitstatus now has command1"s exit status.
Ich denke, dies lässt sich am besten von innen nach außen erklären – Befehl1 führt seine reguläre Ausgabe aus und druckt sie auf stdout (Dateideskriptor 1). Sobald dies erledigt ist, führt printf den Exit-Code von command1 aus und druckt ihn auf seinem stdout. Dieser stdout wird jedoch an den Dateideskriptor 3 umgeleitet.
Während command1 ausgeführt wird, wird sein stdout an weitergeleitet Befehl2 (die Ausgabe von printf schafft es nie zu Befehl2, weil wir sie an Dateideskriptor 3 anstatt an 1 senden, was i s was die Pipe liest).Dann leiten wir die Ausgabe von command2 auf Dateideskriptor 4 um, so dass sie auch außerhalb von Dateideskriptor 1 bleibt – weil wir Dateideskriptor 1 für ein wenig später frei haben möchten, weil wir die printf-Ausgabe auf Dateideskriptor 3 wieder nach unten bringen Dateideskriptor 1 – weil dies die Befehlssubstitution (die Backticks) erfasst und in die Variable eingefügt wird.
Das letzte bisschen Magie ist das erste exec 4>&1
haben wir als separaten Befehl ausgeführt – es öffnet den Dateideskriptor 4 als Kopie der Standardausgabe der externen Shell. Die Befehlssubstitution erfasst alles, was auf Standard geschrieben ist, aus der Perspektive der darin enthaltenen Befehle. Da jedoch die Ausgabe von Befehl2 in Bezug auf die Befehlssubstitution in den Dateideskriptor 4 geht, wird sie durch die Befehlssubstitution nicht erfasst. Sobald die Befehlssubstitution jedoch „beendet“ ist, wird effektiv immer noch der gesamte Dateideskriptor 1 des Skripts aufgerufen.
(Die exec 4>&1
hat Dies ist ein separater Befehl, da es vielen gängigen Shells nicht gefällt, wenn Sie versuchen, in einen Dateideskriptor innerhalb einer Befehlssubstitution zu schreiben, der im Befehl „external“ geöffnet wird, der die Substitution verwendet. Dies ist also der einfachste tragbare Weg um es zu tun.)
Sie können es weniger technisch und spielerisch betrachten, als ob die Ausgaben der Befehle sich gegenseitig überspringen: Befehl1 leitet zu Befehl2 weiter, dann springt die Ausgabe des Druckers über Befehl 2, so dass Befehl 2 ihn nicht „fängt“, und dann springt die Ausgabe von Befehl 2 über und aus th heraus Die Befehlssubstitution genau so, wie printf gerade rechtzeitig landet, um von der Substitution erfasst zu werden, so dass sie in der Variablen landet, und die Ausgabe von Befehl2 wird auf fröhliche Weise in die Standardausgabe geschrieben, genau wie in einer normalen Pipe.
Soweit ich weiß, enthält $?
weiterhin den Rückkehrcode des zweiten Befehls in der Pipe, da Variablenzuweisungen, Befehlsersetzungen und zusammengesetzte Befehle vorhanden sind alle effektiv transparent für den Rückkehrcode des Befehls in ihnen, so dass der Rückgabestatus von Befehl2 weitergegeben werden sollte – dies und ohne zusätzliche Funktion definieren zu müssen, ist meiner Meinung nach eine etwas bessere Lösung als die vorgeschlagene von lesmana.
Gemäß den Vorbehalten, die lesmana erwähnt, ist es möglich, dass command1 irgendwann die Dateideskriptoren 3 oder 4 verwendet. Um also robuster zu sein, würden Sie Folgendes tun:
exec 4>&1 exitstatus=`{ { command1 3>&-; printf $? 1>&3; } 4>&- | command2 1>&4; } 3>&1` exec 4>&-
Beachten Sie, dass ich in meinem Beispiel zusammengesetzte Befehle verwende, aber Subshells (mit ( )
anstelle von { }
funktioniert ebenfalls, ist jedoch möglicherweise weniger effizient.)
Befehle erben Dateideskriptoren vom Prozess Dadurch werden sie gestartet, sodass die gesamte zweite Zeile den Dateideskriptor vier erbt und der zusammengesetzte Befehl gefolgt von 3>&1
den Dateideskriptor drei erbt. Der 4>&-
stellt also sicher, dass der innere zusammengesetzte Befehl den Dateideskriptor vier nicht erbt und der 3>&-
den Dateideskriptor drei nicht erbt Befehl1 erhält eine „sauberere“, standardisiertere Umgebung. Sie können auch das innere 4>&-
neben das 3>&-
verschieben, aber ich denke, warum nicht einfach den Umfang so weit wie möglich einschränken.
Ich bin mir nicht sicher, wie oft die Dateien den Dateideskriptor drei und vier direkt verwenden. Ich denke, die meisten Programme verwenden Syscalls, die derzeit nicht verwendete Dateideskriptoren zurückgeben, aber manchmal schreibt Code in eine Datei Deskriptor 3 direkt, denke ich (ich könnte mir vorstellen, dass ein Programm einen Dateideskriptor überprüft, um festzustellen, ob er geöffnet ist, und ihn verwendet, wenn dies der Fall ist, oder sich entsprechend anders verhält, wenn dies nicht der Fall ist). Letzterer ist also wahrscheinlich am besten beizubehalten im Auge behalten und für allgemeine Fälle verwenden.
Kommentare
- Sieht interessant aus, aber ich kann ‚ Ich weiß nicht genau, was Sie von diesem Befehl erwarten, und mein Computer kann ‚ auch nicht; ich erhalte
-bash: 3: Bad file descriptor
. - @ G-Man Richtig, ich vergesse immer wieder, dass bash keine Ahnung hat, was es ‚ tut, wenn es co Im Gegensatz zu den Shells, die ich normalerweise verwende (die Asche, die mit der Busybox geliefert wird). Ich ‚ werde Sie wissen lassen, wenn ich an eine Problemumgehung denke, die bash glücklich macht. In der Zwischenzeit, wenn Sie ‚ eine Debian-Box zur Hand haben, können Sie sie im Bindestrich ausprobieren, oder wenn Sie ‚ eine Busybox zur Hand haben Ich kann es mit der Busybox ash / sh versuchen.
- @ G-Man Was ich von dem Befehl erwarte und was er in anderen Shells tut, ist, stdout von Befehl1 umzuleiten, damit es nicht ‚ wird nicht von der Befehlssubstitution erfasst, aber sobald es außerhalb der Befehlssubstitution liegt, wird fd3 wieder auf stdout gelöscht, sodass ‚ wie erwartet weitergeleitet wird zu command2.Wenn command1 beendet wird, wird printf ausgelöst und druckt seinen Exit-Status, der durch die Befehlsersetzung in der Variablen erfasst wird. Sehr detaillierte Aufschlüsselung hier: stackoverflow.com/questions/985876/tee-and-exit-status/… Auch , dieser Kommentar von Ihnen liest sich so, als ob er irgendwie beleidigend sein sollte?
- Wo soll ich anfangen? (1) Es tut mir leid, wenn Sie sich beleidigt fühlten. „Sieht interessant aus“ war ernst gemeint; Es wäre großartig, wenn etwas so Kompaktes so funktionieren würde, wie Sie es erwartet hatten. Darüber hinaus habe ich einfach gesagt, dass ich nicht verstanden habe, was Ihre Lösung tun sollte. Ich habe lange mit Unix gearbeitet / gespielt (seit es Linux gab), und wenn ich etwas nicht verstehe, ist das eine rote Fahne, die vielleicht andere Leute werden es auch nicht verstehen und dass es mehr Erklärung braucht (IMNSHO). … (Fortsetzung)
- (Fortsetzung)… Seit Sie “… denken gerne… dass [Sie] so gut wie verstehen alles mehr als die durchschnittliche Person “, sollten Sie sich vielleicht daran erinnern, dass das Ziel von Stack Exchange nicht darin besteht, einen Befehlsschreibdienst zu sein, der Tausende von einmaligen Lösungen für trivial unterschiedliche Fragen liefert. sondern um den Menschen beizubringen, wie sie ihre eigenen Probleme lösen können. Und zu diesem Zweck müssen Sie die Dinge vielleicht so gut erklären, dass eine „durchschnittliche Person“ sie verstehen kann. In Lesmanas Antwort finden Sie ein Beispiel für eine hervorragende Erklärung. … (Fortsetzung)
Antwort
Die obige Lösung von lesmana kann auch ohne den Aufwand von durchgeführt werden Starten verschachtelter Unterprozesse mithilfe von { .. }
(wobei zu beachten ist, dass diese Form gruppierter Befehle immer mit Semikolons abgeschlossen werden muss). So etwas:
{ { { { someprog; echo $? >&3; } | filter >&4; } 3>&1; } | stdintoexitstatus; } 4>&1
Ich habe dieses Konstrukt mit der Dash-Version 0.5.5 und den Bash-Versionen 3.2.25 und 4.2.42 überprüft. Selbst wenn einige Shells { .. }
Gruppierung, es ist immer noch POSIX-kompatibel.
Kommentare
- Dies funktioniert sehr gut mit den meisten Shells, die ich ‚ Ich habe es mit versucht, einschließlich NetBSD sh, pdksh, mksh, dash, bash. Allerdings kann ich ‚ es nicht zum Laufen bringen mit AT & T Ksh (93s +, 93u +) oder zsh (4.3.9, 5.2), auch mit
set -o pipefail
in ksh oder einer beliebigen Anzahl vonwait
Befehle in beiden. Ich denke, es kann teilweise Seien Sie zumindest ein Parsing-Problem für ksh, als ob ich bei der Verwendung von Subshells bleiben würde, dann funktioniert es einwandfrei, aber selbst mit einemif
können Sie die Subshell-Variante für ksh auswählen, aber die zusammengesetzten Befehle belassen für andere schlägt es fehl.
Antwort
Das Folgende ist als Addon zur Antwort von @Patrik gedacht. Falls Sie eine der gängigen Lösungen nicht verwenden können.
Diese Antwort setzt Folgendes voraus:
- Sie haben eine Shell, die noch
set -o pipefail
- Sie möchten eine Pipe für die parallele Ausführung verwenden, also keine temporären Dateien.
- Sie Sie möchten keine zusätzliche Unordnung haben, wenn Sie das Skript unterbrechen, möglicherweise durch einen plötzlichen Stromausfall.
- Diese Lösung sollte relativ einfach zu befolgen und sauber zu lesen sein.
- Sie tun dies Sie möchten keine zusätzlichen Subshells einführen.
- Sie können nicht mit den vorhandenen Dateideskriptoren herumspielen, daher darf stdin / out / err nicht berührt werden (wie Sie können vorübergehend eine neue einführen.)
Zusätzliche Annahmen. Sie können alles loswerden, aber dies beeinträchtigt das Rezept zu sehr, sodass es hier nicht behandelt wird:
- Sie möchten nur wissen, dass alle Befehle in der PIPE den Exit-Code 0 haben.
- Sie benötigen keine zusätzlichen Seitenbandinformationen.
- Ihre Shell wartet auf die Rückgabe aller Pipe-Befehle.
Vorher: foo | bar | baz
Dies gibt jedoch nur den Exit-Code des letzten Befehls zurück (baz
)
Gesucht: $?
darf nicht 0
(true) sein, wenn einer der Befehle in der Pipe fehlgeschlagen ist
Nachher:
TMPRESULTS="`mktemp`" { rm -f "$TMPRESULTS" { foo || echo $? >&9; } | { bar || echo $? >&9; } | { baz || echo $? >&9; } #wait ! read TMPRESULTS <&8 } 9>>"$TMPRESULTS" 8<"$TMPRESULTS" # $? now is 0 only if all commands had exit code 0
Erklärt:
- Mit
mktemp
. Dadurch wird normalerweise sofort eine Datei in/tmp
- erstellt. Diese Tempfile wird dann zum Schreiben an FD 9 und zum Lesen an FD 8 umgeleitet.
- Dann die tempfile wird sofort gelöscht. Es bleibt jedoch geöffnet, bis beide FDs nicht mehr existieren.
- Jetzt wird die Pipe gestartet. Jeder Schritt wird nur dann zu FD 9 hinzugefügt, wenn ein Fehler aufgetreten ist.
- Die
wait
wird fürksh
benötigt. weilksh
else nicht darauf wartet, dass alle Pipe-Befehle beendet werden. Bitte beachten Sie jedoch, dass es unerwünschte Nebenwirkungen gibt, wenn einige Hintergrundaufgaben vorhanden sind. Daher habe ich dies standardmäßig auskommentiert.Wenn das Warten nicht weh tut, können Sie es kommentieren. - Anschließend wird der Inhalt der Datei gelesen. Wenn es leer ist (weil alles funktioniert hat)
read
Gibtfalse
zurück, sodasstrue
einen Fehler anzeigt.
Dies kann als Plugin-Ersatz für verwendet werden Ein einzelner Befehl, der nur Folgendes benötigt:
- Nicht verwendete FDs 9 und 8
- Eine einzelne Umgebungsvariable, die den Namen der temporären Datei enthält.
- Und dies Das Rezept kann an ziemlich jede Shell da draußen angepasst werden, die eine E / A-Umleitung ermöglicht.
- Außerdem ist es ziemlich plattformunabhängig und benötigt keine Dinge wie
/proc/fd/N
BUGs:
Dieses Skript weist einen Fehler auf, falls /tmp
nicht genügend Speicherplatz zur Verfügung steht. Wenn Sie auch Schutz vor diesem künstlichen Fall benötigen, Sie können dies wie folgt tun, dies hat jedoch den Nachteil, dass die Anzahl von 0
in 000
von der Anzahl der Befehle in derPipe, daher ist es etwas komplizierter:
TMPRESULTS="`mktemp`" { rm -f "$TMPRESULTS" { foo; printf "%1s" "$?" >&9; } | { bar; printf "%1s" "$?" >&9; } | { baz; printf "%1s" "$?" >&9; } #wait read TMPRESULTS <&8 [ 000 = "$TMPRESULTS" ] } 9>>"$TMPRESULTS" 8<"$TMPRESULTS"
Hinweise zur Portabilität:
-
ksh
und ähnliche Shells, die nur auf den letzten Pipe-Befehl warten, benötigen diewait
unkommentierte -
, die im letzten Beispiel verwendet wird
printf "%1s" "$?"
anstelle vonecho -n "$?"
, da dies portabler ist. Nicht jede Plattform interpretiert-n
richtig. -
printf "$?"
würde dies ebenfalls tun.printf "%1s"
fängt jedoch einige Eckfälle ab, falls Sie das Skript auf einer wirklich kaputten Plattform ausführen. (Lesen Sie: Wenn Sie zufällig inparanoia_mode=extreme
programmieren.) -
FD 8 und FD 9 können auf Plattformen, die mehrere unterstützen, höher sein Ziffern. AFAIR Eine POSIX-konforme Shell muss nur einzelne Ziffern unterstützen.
-
Wurde mit Debian 8.2
sh
, ,ksh
,ash
,sash
und sogarcsh
Antwort
Dies ist portabel, dh Funktioniert mit jeder POSIX-kompatiblen Shell, erfordert nicht, dass das aktuelle Verzeichnis beschreibbar ist, und ermöglicht die gleichzeitige Ausführung mehrerer Skripte mit demselben Trick.
(foo;echo $?>/tmp/_$$)|(bar;exit $(cat /tmp/_$$;rm /tmp/_$$))
Bearbeiten: Hier ist eine stärkere Version nach Gilles „Kommentaren:
(s=/tmp/.$$_$RANDOM;((foo;echo $?>$s)|(bar)); exit $(cat $s;rm $s))
Edit2: und hier ist eine etwas leichtere Variante nach zweifelhaftem Kommentar:
(s=/tmp/.$$_$RANDOM;{foo;echo $?>$s;}|bar; exit $(cat $s;rm $s))
Kommentare
- Dies funktioniert aus mehreren Gründen nicht ‚. 1. Die temporäre Datei kann gelesen werden, bevor ‚ geschrieben wird. 2. Das Erstellen einer temporären Datei in einem freigegebenen Verzeichnis mit einem vorhersehbaren Namen ist unsicher (triviales DoS, Symlink Race). 3. Wenn dasselbe Skript diesen Trick mehrmals verwendet, verwendet es ‚ immer denselben Dateinamen. Um 1 zu lösen, lesen Sie die Datei nach Abschluss der Pipeline. Verwenden Sie zum Lösen von 2 und 3 eine temporäre Datei mit einem zufällig generierten Namen oder in einem privaten Verzeichnis.
- +1 Nun, das $ {PIPESTATUS [0]} ist einfacher, aber die Grundidee hier funktioniert, wenn Man kennt die Probleme, die Gilles erwähnt.
- Sie können einige Unterschalen speichern:
(s=/tmp/.$$_$RANDOM;{foo;echo $?>$s;}|bar; exit $(cat $s;rm $s))
. @Johan: Ich bin damit einverstanden, dass ‚ mit Bash einfacher ist, aber in einigen Kontexten lohnt es sich zu wissen, wie man Bash vermeidet.
Antwort
Mit einiger Vorsicht sollte dies funktionieren:
foo-status=$(mktemp -t) (foo; echo $? >$foo-status) | bar foo_status=$(cat $foo-status)
Kommentare
- Wie wäre es mit einer Bereinigung wie jlliagre? ‚ Lassen Sie keine Datei mit dem Namen foo-status zurück?
- @Johan: Wenn Sie meinen Vorschlag bevorzugen, geben Sie ‚ zögere nicht, darüber abzustimmen;) Zusätzlich zum Verlassen einer Datei hat es den Vorteil, dass mehrere Prozesse dies gleichzeitig ausführen können und das aktuelle Verzeichnis nicht beschreibbar sein muss.
Antwort
Der folgende „if“ -Block wird nur ausgeführt, wenn „command“ erfolgreich war:
if command; then # ... fi
Insbesondere können Sie Folgendes ausführen:
haconf_out=/path/to/some/temporary/file if haconf -makerw > "$haconf_out" 2>&1; then grep -iq "Cluster already writable" "$haconf_out" # ... fi
Damit wird haconf -makerw
ausgeführt und speichern Sie stdout und stderr in „$ haconf_out“. Wenn der von haconf
zurückgegebene Wert wahr ist, wird der „if“ -Block ausgeführt und grep
liest „$ haconf_out“ und versucht es um es mit „Cluster bereits beschreibbar“ abzugleichen.
Beachten Sie, dass sich Pipes automatisch selbst bereinigen. Bei der Umleitung müssen Sie vorsichtig sein, um „$ haconf_out“ zu entfernen, wenn Sie fertig sind.
Nicht so elegant wie pipefail
, aber eine legitime Alternative, wenn diese Funktionalität ist nicht in Reichweite.
Antwort
Alternate example for @lesmana solution, possibly simplified. Provides logging to file if desired. ===== $ cat z.sh TEE="cat" #TEE="tee z.log" #TEE="tee -a z.log" exec 8>&- 9>&- { { { { #BEGIN - add code below this line and before #END ./zz.sh echo ${?} 1>&8 # use exactly 1x prior to #END #END } 2>&1 | ${TEE} 1>&9 } 8>&1 } | exit $(read; printf "${REPLY}") } 9>&1 exit ${?} $ cat zz.sh echo "my script code..." exit 42 $ ./z.sh; echo "status=${?}" my script code... status=42 $
Antwort
(mindestens mit bash) kombiniert mit set -e
kann eine Subshell verwendet werden, um Pipefail explizit zu emulieren und bei Pipe-Fehler zu beenden
set -e foo | bar ( exit ${PIPESTATUS[0]} ) rest of program
Wenn also foo
aus irgendeinem Grund fehlschlägt, wird der Rest des Programms nicht ausgeführt und das Skript wird mit dem entsprechenden Fehlercode beendet. (Dies setzt voraus, dass foo
einen eigenen Fehler ausgibt, der ausreicht, um den Grund des Fehlers zu verstehen.)
Antwort
BEARBEITEN : Diese Antwort ist falsch, aber interessant, daher lasse ich sie für die Zukunft Referenz.
Durch Hinzufügen einer !
zum Befehl wird der Rückkehrcode invertiert.
http://tldp.org/LDP/abs/html/exit-status.html
# =========================================================== # # Preceding a _pipe_ with ! inverts the exit status returned. ls | bogus_command # bash: bogus_command: command not found echo $? # 127 ! ls | bogus_command # bash: bogus_command: command not found echo $? # 0 # Note that the ! does not change the execution of the pipe. # Only the exit status changes. # =========================================================== #
Kommentare
- Ich denke, dies hat nichts damit zu tun. In Ihrem Beispiel möchte ich den Exit-Code von
ls
kennen und den Exit-Code vonbogus_command
- Ich schlage vor, diese Antwort zurückzuziehen.
- Nun, es scheint, dass ich ‚ ein Idiot bin. Ich ‚ habe dies tatsächlich in einem Skript verwendet, bevor ich dachte, es würde das tun, was das OP wollte. Gut, dass ich es ‚ nicht für irgendetwas Wichtiges verwendet habe