Ich möchte einen Befehl mit langer Laufzeit in Bash ausführen und sowohl den Exit-Status erfassen als auch die Ausgabe abschlagen .
Also mache ich das:
command | tee out.txt
ST=$?
Das Problem ist, dass die Variable ST den Exit-Status von tee
und nicht von Befehl erfasst . Wie kann ich das lösen?
Beachten Sie, dass der Befehl lange ausgeführt wird und das Umleiten der Ausgabe in eine Datei zur späteren Anzeige keine gute Lösung für mich ist.
bash
shell
error-handling
pipe
Flybywire
quelle
quelle
Antworten:
Es gibt eine interne Bash-Variable namens
$PIPESTATUS
; Es ist ein Array, das den Exit-Status jedes Befehls in Ihrer letzten Vordergrund-Pipeline von Befehlen enthält.Oder eine andere Alternative, die auch mit anderen Shells (wie zsh) funktioniert, wäre das Aktivieren von Pipefail:
Die erste Option ist nicht mit der Arbeit
zsh
auf ein wenig wegen Bit andere Syntax.quelle
exit ${PIPESTATUS[0]}
.Die Verwendung von Bashs
set -o pipefail
ist hilfreichquelle
( set -o pipefail; command | tee out.txt ); ST=$?
set -o pipefail
den Befehlset +o pipefail
ausführen und anschließend die Option deaktivieren.-o pipefail
würde er wissen, ob die Pipe ausfällt, aber wenn sowohl 'Befehl' als auch 'Abschlag' fehlschlagen, würde er den Exit-Code von 'Abschlag' erhalten.Dumme Lösung: Verbinden Sie sie über eine Named Pipe (mkfifo). Dann kann der Befehl als zweiter ausgeführt werden.
quelle
mkfifo
und stattdessen benötigen,mknod -p
wenn ich mich richtig erinnere.mkfifo
oder hatmknod -p
: In meinem Fall war der richtige Befehl zum Erstellen einer Pipe-Dateimknod FILE_NAME p
.Es gibt ein Array, das Ihnen den Exit-Status jedes Befehls in einer Pipe gibt.
quelle
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:
Sie möchten den Exit-Status von
someprog
und die Ausgabe vonfilter
.Hier ist meine Lösung:
In meiner Antwort auf dieselbe Frage auf unix.stackexchange.com finden Sie eine ausführliche Erklärung und eine Alternative ohne Unterschalen und einige Einschränkungen.
quelle
Durch Kombinieren
PIPESTATUS[0]
und das Ergebnis der Ausführung desexit
Befehls in einer Subshell können Sie direkt auf den Rückgabewert Ihres ursprünglichen Befehls zugreifen:command | tee ; ( exit ${PIPESTATUS[0]} )
Hier ist ein Beispiel:
werde dir geben:
return value: 1
quelle
VALUE=$(might_fail | piping)
das PIPESTATUS nicht in der Master-Shell setzt, sondern dessen Fehlerlevel setzt. Mit:VALUE=$(might_fail | piping; exit ${PIPESTATUS[0]})
Ich will wollen, ich wollte.command_might_fail | grep -v "line_pattern_to_exclude" || exit ${PIPESTATUS[0]}
falls nicht tee sondern grepIch wollte also eine Antwort wie die von Lesmana beisteuern, aber ich denke, meine ist vielleicht eine etwas einfachere und etwas vorteilhaftere reine Bourne-Shell-Lösung:
Ich denke, dies lässt sich am besten von innen nach außen erklären - Befehl1 wird ausgeführt und druckt seine reguläre Ausgabe auf stdout (Dateideskriptor 1). Sobald dies erledigt ist, wird printf den Exit-Code von icommand1 ausführen und auf seinem stdout drucken, aber dieser stdout wird umgeleitet Dateideskriptor 3.
Während Befehl1 ausgeführt wird, wird seine Standardausgabe an Befehl2 weitergeleitet (die Ausgabe von printf gelangt nie zu Befehl2, da wir sie an Dateideskriptor 3 anstatt an 1 senden, was die Pipe liest). Dann leiten wir die Ausgabe von command2 in den Dateideskriptor 4 um, so dass sie auch außerhalb des Dateideskriptors 1 bleibt - weil wir den Dateideskriptor 1 für ein wenig später frei haben möchten, weil wir die printf-Ausgabe auf Dateideskriptor 3 wieder in den Dateideskriptor zurückbringen 1 - weil dies die Befehlssubstitution (die Backticks) erfasst und in die Variable eingefügt wird.
Das letzte bisschen Magie ist, dass
exec 4>&1
wir es zuerst als separaten Befehl getan haben - es öffnet den Dateideskriptor 4 als Kopie des Standardout 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 - jedoch einmal Wenn die Befehlssubstitution "beendet" wird, wird effektiv immer noch der gesamte Dateideskriptor 1 des Skripts aufgerufen.(Das
exec 4>&1
muss ein separater Befehl sein, da es vielen gängigen Shells nicht gefällt, wenn Sie versuchen, in einen Dateideskriptor innerhalb einer Befehlssubstitution zu schreiben, der im "externen" Befehl geöffnet wird, der die Substitution verwendet einfachste tragbare Möglichkeit, dies zu tun.)Sie können es weniger technisch und spielerisch betrachten, als ob sich die Ausgaben der Befehle gegenseitig überspringen: Befehl1 leitet zu Befehl2 weiter, dann springt die Ausgabe des Drucks über Befehl 2, sodass Befehl2 ihn nicht abfängt, und dann Die Ausgabe von Befehl 2 springt über die Befehlssubstitution hinaus und verlässt sie, sobald printf gerade rechtzeitig landet, um von der Substitution erfasst zu werden, sodass sie in der Variablen landet, und die Ausgabe von Befehl2 wird auf fröhliche Weise in die Standardausgabe geschrieben in einem normalen Rohr.
Soweit ich weiß,
$?
wird der Rückkehrcode des zweiten Befehls in der Pipe weiterhin enthalten sein, da Variablenzuweisungen, Befehlsersetzungen und zusammengesetzte Befehle für den Rückkehrcode des darin enthaltenen Befehls effektiv transparent sind, sodass der Rückgabestatus von Befehl2 sollte verbreitet werden - dies und das Fehlen einer zusätzlichen Funktion ist der Grund, warum ich denke, dass dies eine etwas bessere Lösung sein könnte als die von Lesmana vorgeschlagene.Gemäß den Vorbehalten, die Lesmana erwähnt, ist es möglich, dass Befehl1 irgendwann die Dateideskriptoren 3 oder 4 verwendet. Um also robuster zu sein, würden Sie Folgendes tun:
Beachten Sie, dass ich in meinem Beispiel zusammengesetzte Befehle verwende, aber Subshells (die Verwendung von
( )
anstelle von{ }
funktioniert auch, obwohl dies möglicherweise weniger effizient ist.)Befehle erben Dateideskriptoren von dem Prozess, der sie startet, sodass die gesamte zweite Zeile den Dateideskriptor vier erbt und der zusammengesetzte Befehl gefolgt von
3>&1
dem Dateideskriptor drei. Das4>&-
stellt also sicher, dass der innere zusammengesetzte Befehl den Dateideskriptor vier und den3>&-
Dateideskriptor drei nicht erbt, sodass command1 eine sauberere, standardisiertere Umgebung erhält. Sie könnten auch das Innere4>&-
neben das verschieben3>&-
, 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 direkt in den Dateideskriptor 3, I. Vermutung (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). Letzteres ist daher wahrscheinlich am besten zu beachten und für allgemeine Fälle zu verwenden.
quelle
In Ubuntu und Debian können Sie
apt-get install moreutils
. Dies enthält ein Hilfsprogramm namensmispipe
, das den Exit-Status des ersten Befehls in der Pipe zurückgibt.quelle
Im Gegensatz zur Antwort von @ cODAR gibt dies den ursprünglichen Exit-Code des ersten Befehls zurück und nicht nur 0 für Erfolg und 127 für Fehler. Aber wie @Chaoran betonte, können Sie einfach anrufen
${PIPESTATUS[0]}
. Es ist jedoch wichtig, dass alles in Klammern steht.quelle
Außerhalb von Bash können Sie Folgendes tun:
Dies ist beispielsweise in Ninja-Skripten nützlich, in denen die Shell erwartet wird
/bin/sh
.quelle
PIPESTATUS [@] muss unmittelbar nach der Rückkehr des Pipe-Befehls in ein Array kopiert werden. Alle Lesevorgänge von PIPESTATUS [@] löschen den Inhalt. Kopieren Sie es in ein anderes Array, wenn Sie den Status aller Pipe-Befehle überprüfen möchten. "$?" ist der gleiche Wert wie das letzte Element von "$ {PIPESTATUS [@]}", und das Lesen scheint "$ {PIPESTATUS [@]}" zu zerstören, aber ich habe dies nicht absolut überprüft.
Dies funktioniert nicht, wenn sich das Rohr in einer Unterschale befindet. Eine Lösung für dieses Problem finden
Sie unter bash pipestatus im Befehl backticked?
quelle
Der einfachste Weg, dies in einfacher Bash zu tun, ist die Verwendung der Prozesssubstitution anstelle einer Pipeline eine . Es gibt verschiedene Unterschiede, die für Ihren Anwendungsfall jedoch wahrscheinlich keine große Rolle spielen:
pipefail
Option und diePIPESTATUS
Variable sind für die Prozessersetzung irrelevant.Mit der Prozessersetzung startet bash den Prozess einfach und vergisst ihn, er ist nicht einmal sichtbar in
jobs
.Erwähnte Unterschiede beiseite,
consumer < <(producer)
undproducer | consumer
sind im Wesentlichen gleichwertig.Wenn Sie umdrehen möchten, welcher der "Haupt" -Prozess ist, drehen Sie einfach die Befehle und die Richtung der Ersetzung um
producer > >(consumer)
. In Ihrem Fall:Beispiel:
Wie gesagt, es gibt Unterschiede zum Pipe-Ausdruck. Der Prozess hört möglicherweise nie auf zu laufen, es sei denn, er reagiert empfindlich auf das Schließen des Rohrs. Insbesondere kann es sein, dass weiterhin Dinge in Ihr Standard geschrieben werden, was verwirrend sein kann.
quelle
Reine Schalenlösung:
Und jetzt mit dem zweiten
cat
ersetzt durchfalse
:Bitte beachten Sie, dass die erste Katze ebenfalls ausfällt, da sie standardmäßig geschlossen wird. Die Reihenfolge der fehlgeschlagenen Befehle im Protokoll ist in diesem Beispiel korrekt, aber verlassen Sie sich nicht darauf.
Diese Methode ermöglicht das Erfassen von stdout und stderr für die einzelnen Befehle, sodass Sie diese dann auch in eine Protokolldatei kopieren können, wenn ein Fehler auftritt, oder sie einfach löschen können, wenn kein Fehler vorliegt (wie die Ausgabe von dd).
quelle
Basis ist die Antwort von @ brian-s-wilson; diese Bash-Helfer-Funktion:
verwendet also:
1: get_bad_things muss erfolgreich sein, sollte aber keine Ausgabe erzeugen; aber wir wollen die Ausgabe sehen, die es produziert
2: Alle Pipelines müssen erfolgreich sein
quelle
Es kann manchmal einfacher und klarer sein, einen externen Befehl zu verwenden, als sich mit den Details von Bash zu befassen. Die Pipeline wird aus der minimalen Prozessskriptsprache execline mit dem Rückkehrcode des zweiten Befehls * beendet, genau wie eine
sh
Pipeline, aber im Gegensatzsh
dazu ermöglicht sie das Umkehren der Richtung der Pipe, sodass wir den Rückkehrcode des Produzenten erfassen können Prozess (das Folgende ist alles in dersh
Befehlszeile, aber mitexecline
installiert):Die Verwendung
pipeline
hat dieselben Unterschiede zu nativen Bash-Pipelines wie die in Antwort Nr. 43972501 verwendete Bash-Prozess-Substitution .* Beendet eigentlich
pipeline
überhaupt nicht, es sei denn, es liegt ein Fehler vor. Es wird im zweiten Befehl ausgeführt, daher ist es der zweite Befehl, der die Rückgabe ausführt.quelle