Abrufstatus des Prozesses abrufen, der ' an einen anderen

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 Option pipefail.
  • 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:

  1. 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.
  2. 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.
  3. 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.
  4. A. Pipe wird erstellt und die Befehle links (#part5 und #part6) und rechts (filter >&4) ausgeführt werden. Die Ausgabe von filter wird an den Dateideskriptor 4 umgeleitet. In #part1 wurde der Dateideskriptor 4 an stdout umgeleitet. Dies bedeutet, dass die Ausgabe von filter die Standardausgabe des gesamten Konstrukts ist.
  5. 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.
  6. someprog ist hingerichtet. Der Exit-Status wird in #part5 übernommen. Die Standardausgabe wird von der Pipe in #part4 übernommen und an filter weitergeleitet. Die Ausgabe von filter 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

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 über bash 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/ffgetopt (oder ähnliches) Parsen mit optarg, dem nächsten Element in ARGV, als Argument für -o, daher schlägt es fehl. Wenn Sie einen Wrapper erstellen (z. B. bash-pf, der gerade exec /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 Sie mktemp , 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 von wait 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 einem if 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ür ksh benötigt. weil ksh 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 Gibt false zurück, sodass true 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 die wait unkommentierte

  • , die im letzten Beispiel verwendet wird printf "%1s" "$?" anstelle von echo -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 in paranoia_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 sogar csh

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 von bogus_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

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.