Frage

Ich möchte eine lange Laufbefehl in Bash auszuführen, und sowohl seine Exit-Status zu erfassen und tee seine Ausgabe.

Also ich dies tun:

command | tee out.txt
ST=$?

Das Problem ist, dass die Variable ST den Exit-Status von tee und nicht die von Befehl erfasst. Wie kann ich dieses Problem lösen?

Beachten Sie, dass Befehl ist lang anhaltend und die Ausgabe in eine Datei umleiten, es zu einem späteren Zeitpunkt anzuzeigen ist keine gute Lösung für mich.

War es hilfreich?

Lösung

Es gibt eine interne Bash Variable namens $PIPESTATUS; es ist ein Array, das den Exit-Status von jedem Befehl in der letzten Vordergrund Pipeline von Befehlen.

hält
<command> | tee out.txt ; test ${PIPESTATUS[0]} -eq 0

Oder eine andere Alternative, die auch mit anderen Schalen arbeitet (wie zsh) wäre pipefail zu aktivieren:

set -o pipefail
...

Die erste Option tut nicht Arbeit mit zsh aufgrund einer etwas anderen Syntax.

Andere Tipps

bash set -o pipefail ist hilfreich

  

pipefail: der Rückgabewert einer Pipeline ist der Status      Der letzte Befehl mit einem nicht-Null-Status zu verlassen,      oder Null, wenn kein Befehl mit einem Nicht-Null-Status verlassen

Dumb Lösung: Verbinden sie durch eine Named Pipe (mkfifo). Dann kann der Befehl zweite ausgeführt werden.

 mkfifo pipe
 tee out.txt < pipe &
 command > pipe
 echo $?

Es gibt ein Array, das Ihnen den Exit-Status jeden Befehl in einem Rohr gibt.

$ cat x| sed 's///'
cat: x: No such file or directory
$ echo $?
0
$ cat x| sed 's///'
cat: x: No such file or directory
$ echo ${PIPESTATUS[*]}
1 0
$ touch x
$ cat x| sed 's'
sed: 1: "s": substitute pattern can not be delimited by newline or backslash
$ echo ${PIPESTATUS[*]}
0 1

Diese Lösung funktioniert ohne bash spezifische Merkmale oder temporäre Dateien. Bonus:. Am Ende des Exit-Status ist eigentlich ein Exit-Status und nicht eine Zeichenfolge in einer Datei

Situation:

someprog | filter

Sie wollen 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

echo $?

Siehe meine Antwort auf die gleiche Frage auf unix.stackexchange.com für eine ausführliche Erklärung und eine Alternative ohne Subshells und einige Einsprüche.

Durch die Kombination von PIPESTATUS[0] und das Ergebnis in einer Subshell des exit Befehl auszuführen, können Sie direkt den Rückgabewert Ihres ersten Kommandos:

command | tee ; ( exit ${PIPESTATUS[0]} )

Hier ist ein Beispiel:

# the "false" shell built-in command returns 1
false | tee ; ( exit ${PIPESTATUS[0]} )
echo "return value: $?"

geben Sie:

return value: 1

Also wollte ich eine Antwort wie Lesmana des beitragen, aber ich glaube, mir ist vielleicht ein wenig einfacher und etwas vorteilhaftere pure-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, das Beste von innen nach außen erklärt - command1 wird ausgeführt, und seine reguläre Ausgabe auf stdout (Dateideskriptor 1) drucken, dann sobald es fertig ist, wird printf ausführen und Exit-Code des Drucks icommand1 auf seiner stdout, aber das stdout umgeleitet wird Descriptor 3 einzureichen.

Während command1 läuft, wird seine stdout geleitet wird, COMMAND2 (printf die Ausgabe macht es nie COMMAND2, weil wir es senden Descriptor-Datei 3 statt 1, das ist, was das Rohr liest). Dann umleiten wir command2 des Ausgabedeskriptor 4, Datei, so dass es auch aus Dateideskriptors bleibt 1 - weil wir Dateideskriptor 1 frei für etwas später wollen, weil wir die printf Ausgabe auf Dateideskriptor bringen 3 wieder nach unten in Dateideskriptor . 1 - denn das ist es, was der Befehl Substitution (die Backticks), erfassen wird, und das ist, was in die Variable platziert werden erhalten

Die letzten bisschen Magie ist, dass erstes exec 4>&1 wir als separaten Befehl taten - es öffnet stdout Dateideskriptors 4 als Kopie des extern Shell. Kommandosubstitution erfassen wird, was auch immer auf die Standardausgabe aus der Perspektive der Befehle innerhalb geschrieben steht - aber da command2 der Ausgabedeskriptor Datei wird 4 bis der Befehl Substitution betrifft, so wird der Befehl Substitution nicht erfassen - aber sobald es „out“ des Befehls Substitution wird es effektiv noch insgesamt Dateideskriptor 1 des Skripts gehen.

(Der exec 4>&1 hat ein separater Befehl sein, weil viele gemeinsamen Muscheln mögen es nicht, wenn Sie versuchen, in einem Befehl Substitution an einen Dateideskriptor zu schreiben, das heißt in dem „externen“ Befehl geöffnet, die die Substitution verwendet. das ist also die einfachste tragbare Art und Weise, es zu tun.)

Sie können in einem technisch weniger versierte es auch dreht und auf spielerische Weise, als ob die Ausgaben der Befehle einander leapfrogging: command1 Rohre COMMAND2, dann die Ausgabe des printf springt über Kommando 2, so dass command2 es nicht fangen und befehlen dann 2 des Ausgang springt über und aus der Kommandosubstitution wie printf landet gerade rechtzeitig durch die Substitution eingefangen werden, so dass es in den variablen endet, und command2 des Ausgang geht auf seiner fröhlichen Art und Weise auf die Standardausgabe geschrieben , ebenso wie in einem normalen Rohr.

Auch, wie ich es verstehe, $? noch den Return-Code des zweiten Befehls in dem Rohr enthalten, da Variablenzuweisungen, Befehlsersetzungen, und die Verbindung Befehle an den Rückkehrcode des Befehls in sie alle effektiv transparent sind, so der Rückgabestatus von command2 propagiert heraus bekommen sollte - das, und nicht mit einer zusätzlichen Funktion zu definieren, ist, warum ich denke, das eine etwas bessere Lösung als die von Lesmana vorgeschlagen sein könnte

.

Pro die Einsprüche Lesmana erwähnt, ist es möglich, dass command1 irgendwann Filedeskriptoren 3 oder 4 am Ende mit, so robuster zu sein, würden Sie tun:

exec 4>&1
exitstatus=`{ { command1 3>&-; printf $? 1>&3; } 4>&- | command2 1>&4; } 3>&1`
exec 4>&-

Beachten Sie, dass ich Verbindung Befehle in meinem Beispiel verwenden, aber Subshells (mit ( ) statt { } wird auch funktionieren, wenn auch vielleicht weniger effizient sein kann.)

Befehle erben Filedeskriptoren aus dem Prozess, der sie startet, so dass die gesamte zweite Zeile Dateideskriptors vier, und die Verbindung Befehl von 3>&1 gefolgt erbt den Dateideskriptor drei erben. So macht die 4>&- sicher, dass die innere Verbindung Befehl nicht Dateideskriptors vier, erben und die 3>&- nicht Dateideskriptors drei, erben so command1 bekommt eine ‚sauberere‘, mehr Standardumgebung. Sie könnten auch den inneren 4>&- neben dem 3>&- bewegen, aber ich meine, warum nicht nur ihren Umfang begrenzenviel wie möglich.

Ich bin nicht sicher, wie oft Dinge Dateideskriptors drei und vier direkt verwenden - ich die meiste Zeit denke Programme syscalls verwenden, das nicht-used-at-the-Moment return Filedeskriptoren, aber manchmal Code schreibt Descriptor 3 in Datei direkt, denke ich (ich ein Programm vorstellen könnte einen Dateideskriptor zu überprüfen, ob es geöffnet ist, und dessen Verwendung, wenn sie ist, oder anders entsprechend zu verhalten, wenn es nicht). So letztere ist wahrscheinlich am besten im Auge zu behalten und für allgemeine Zwecke Fälle verwenden.

In Ubuntu und Debian, können Sie apt-get install moreutils. Dies enthält ein Programm namens mispipe, die den Exit-Status des ersten Befehls in dem Rohr zurück.

PIPESTATUS [@] muss unmittelbar nach dem Pipe-Befehl wieder in ein Array kopiert werden. Alle liest von PIPESTATUS [@] wird den Inhalt löschen. Kopieren Sie es in ein anderes Array, wenn Sie auf das Überprüfen des Status aller Rohr Befehle planen. "$?" ist der gleiche Wert wie das letzte Element von "$ {PIPESTATUS [@]}", und es zu lesen scheint „$ {PIPESTATUS [@]}“ zu zerstören, aber ich habe nicht unbedingt dies bestätigt.

declare -a PSA  
cmd1 | cmd2 | cmd3  
PSA=( "${PIPESTATUS[@]}" )

Das wird nicht funktionieren, wenn das Rohr in einer Unterschale ist. Für eine Lösung für dieses Problem,
finden Sie unter bash PIPESTATUS in backticked Befehl?

(command | tee out.txt; exit ${PIPESTATUS[0]})

Im Gegensatz zu @ Codar Antwort dies gibt den ursprünglichen Exit-Code des ersten Befehls und nicht nur 0 für Erfolg und 127 für das Scheitern. Aber wie @Chaoran wies darauf hin, können Sie einfach ${PIPESTATUS[0]} nennen. Es ist jedoch wichtig, dass alle in Klammern gesetzt wird.

Außerhalb von bash, können Sie tun:

bash -o pipefail  -c "command1 | tee output"

Dies ist nützlich, zum Beispiel in Ninja-Skripte, wo der Schal /bin/sh werden soll.

Der einfachste Weg, dies im Klar bash zu tun ist, a href zu verwenden <= "https://www.gnu.org/software/bash/manual/bashref.html#Process-Substitution" rel = "nofollow noreferrer" > Prozess Substitution anstelle einer Pipeline. Es gibt einige Unterschiede, aber sie wahrscheinlich für Ihren Anwendungsfall nicht sehr viel Materie:

  • Wenn eine Pipeline ausgeführt wird, bash wartet, bis alle Prozesse abgeschlossen.
  • Senden von Strg-C zu bash macht es alle Prozesse einer Pipeline töten, nicht nur die wichtigste.
  • Die pipefail Option und die PIPESTATUS Variablen sind Prozess Substitution irrelevant.
  • Möglicherweise mehr

Mit Prozess Substitution, bash beginnt nur den Prozess und vergisst es, es ist nicht einmal sichtbar in jobs.

Erwähnt Unterschiede beiseite, consumer < <(producer) und producer | consumer sind im Wesentlichen gleichwertig.

Wenn Sie spiegeln möchten, welches das „Haupt“ Prozess ist, drehen Sie einfach die Befehle und die Richtung der Substitution producer > >(consumer). In Ihrem Fall:

command > >(tee out.txt)

Beispiel:

$ { echo "hello world"; false; } > >(tee out.txt)
hello world
$ echo $?
1
$ cat out.txt
hello world

$ echo "hello world" > >(tee out.txt)
hello world
$ echo $?
0
$ cat out.txt
hello world

Wie gesagt, gibt es Unterschiede von dem Rohr Ausdruck. Der Prozess kann nie aufhören zu laufen, es sei denn, es mit dem Rohrschließ empfindlich ist. Insbesondere kann es schriftlich Dinge zu Ihrem stdout hält, was verwirrend sein kann.

Reine Shell-Lösung:

% rm -f error.flag; echo hello world \
| (cat || echo "First command failed: $?" >> error.flag) \
| (cat || echo "Second command failed: $?" >> error.flag) \
| (cat || echo "Third command failed: $?" >> error.flag) \
; test -s error.flag  && (echo Some command failed: ; cat error.flag)
hello world

Und jetzt mit dem zweiten cat durch false ersetzt:

% rm -f error.flag; echo hello world \
| (cat || echo "First command failed: $?" >> error.flag) \
| (false || echo "Second command failed: $?" >> error.flag) \
| (cat || echo "Third command failed: $?" >> error.flag) \
; test -s error.flag  && (echo Some command failed: ; cat error.flag)
Some command failed:
Second command failed: 1
First command failed: 141

Bitte beachten Sie die erste Katze auch nicht, weil es stdout auf sie geschlossen wird. Die Reihenfolge der ausgefallenen Befehle im Protokoll in diesem Beispiel korrekt ist, aber verlassen Sie sich nicht darauf.

Diese Methode ermöglicht stdout und stderr für die einzelnen Befehle erfassen, so dass Sie dann, dass auch in eine Log-Datei-Dump können, wenn ein Fehler auftritt, oder einfach löschen, wenn kein Fehler (wie die Ausgabe von dd).

Base auf @ brian-s-wilson ‚s Antwort; diese bash Hilfsfunktion:

pipestatus() {
  local S=("${PIPESTATUS[@]}")

  if test -n "$*"
  then test "$*" = "${S[*]}"
  else ! [[ "${S[@]}" =~ [^0\ ] ]]
  fi
}

verwendet also:

1: get_bad_things muss erfolgreich sein, aber es sollte keine Ausgabe zu erzeugen; aber wir wollen Ausgabe sehen, dass es produziert

get_bad_things | grep '^'
pipeinfo 0 1 || return

2: alle Pipeline muss gelingen

thing | something -q | thingy
pipeinfo || return

Es kann manchmal einfacher und klarer sein, einen externen Befehl zu verwenden, anstatt in die Details der bash zu graben. Pipeline , die aus der minimalen Prozess Skriptsprache execline , tritt mit dem Return-Code des zweiten Befehls *, wie nur eine sh Pipeline der Fall ist, aber im Gegensatz zu sh, ermöglicht es Umkehren die Richtung des Rohres, so dass wir den Rückgabecode des Herstellers Prozess erfassen kann (die unten alle auf der sh Kommandozeile, aber mit execline installiert ist):

$ # using the full execline grammar with the execlineb parser:
$ execlineb -c 'pipeline { echo "hello world" } tee out.txt'
hello world
$ cat out.txt
hello world

$ # for these simple examples, one can forego the parser and just use "" as a separator
$ # traditional order
$ pipeline echo "hello world" "" tee out.txt 
hello world

$ # "write" order (second command writes rather than reads)
$ pipeline -w tee out.txt "" echo "hello world"
hello world

$ # pipeline execs into the second command, so that's the RC we get
$ pipeline -w tee out.txt "" false; echo $?
1

$ pipeline -w tee out.txt "" true; echo $?
0

$ # output and exit status
$ pipeline -w tee out.txt "" sh -c "echo 'hello world'; exit 42"; echo "RC: $?"
hello world
RC: 42
$ cat out.txt
hello world

Mit pipeline hat die gleichen Unterschiede zu nativem bash Pipelines als Ersatz bash Prozess verwendet in Antwort # 43972501 .

* Eigentlich ist pipeline nicht verlassen, es sei denn es einen Fehler gibt. Er führt in den zweiten Befehl, so ist es der zweite Befehl, der die Rückkehr der Fall ist.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top