Ich habe zwei Prozesse foo
und bar
, verbunden mit einer Pipe:
$ foo | bar
bar
verlässt immer 0; Ich interessiere mich für den Exit-Code von foo
. Gibt es eine Möglichkeit, daran heranzukommen?
Ich habe zwei Prozesse foo
und bar
, verbunden mit einer Pipe:
$ foo | bar
bar
verlässt immer 0; Ich interessiere mich für den Exit-Code von foo
. Gibt es eine Möglichkeit, daran heranzukommen?
Antworten:
Wenn Sie verwenden bash
, können Sie die PIPESTATUS
Array-Variable verwenden, um den Beendigungsstatus jedes Elements der Pipeline abzurufen.
$ false | true
$ echo "${PIPESTATUS[0]} ${PIPESTATUS[1]}"
1 0
Wenn Sie verwenden zsh
, wird das Array aufgerufen pipestatus
(Groß- und Kleinschreibung!) Und die Array-Indizes beginnen bei eins:
$ false | true
$ echo "${pipestatus[1]} ${pipestatus[2]}"
1 0
So kombinieren Sie sie in einer Funktion auf eine Weise, bei der 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 den obigen Befehl in bash
oder aus, zsh
und Sie erhalten die gleichen Ergebnisse. es wird nur eines von retval_bash
und retval_zsh
gesetzt. Der andere wird leer sein. Dies würde es einer Funktion ermöglichen, mit zu enden return $retval_bash $retval_zsh
(beachten Sie das Fehlen von Anführungszeichen!).
pipestatus
in zsh. Leider haben andere Shells diese Funktion nicht.
echo "$pipestatus[1]" "$pipestatus[2]"
.
if [ `echo "${PIPESTATUS[@]}" | tr -s ' ' + | bc` -ne 0 ]; then echo FAIL; fi
Es gibt drei Möglichkeiten, dies zu tun:
Die erste Möglichkeit besteht darin, die pipefail
Option ( ksh
, zsh
oder bash
) festzulegen . Dies ist die einfachste und setzt im Grunde den Beendigungsstatus $?
auf den Beendigungscode des letzten Programms, um das Programm zu beenden, der nicht Null ist (oder Null, wenn alle erfolgreich beendet wurden).
$ false | true; echo $?
0
$ set -o pipefail
$ false | true; echo $?
1
Bash hat auch eine Array-Variable namens $PIPESTATUS
( $pipestatus
in zsh
), die 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 bestimmten Wert in der Pipeline abzurufen, den Sie benötigen.
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
ksh
, aber von einem kurzen Blick auf die Manpage her unterstützt es nichts $PIPESTATUS
oder ähnliches. Die pipefail
Option wird jedoch unterstützt.
LOG=$(failed_command | successful_command)
Diese Lösung funktioniert ohne bash-spezifische Funktionen oder temporäre Dateien. Bonus: Am Ende ist der Beendigungsstatus tatsächlich ein Beendigungsstatus und keine Zeichenfolge in einer Datei.
Lage:
someprog | filter
Sie möchten 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 exit status von someprog
als exit status des Konstrukts.
Dieses Konstrukt funktioniert auch mit einfacher Befehlsgruppierung {...}
anstelle von Subshells (...)
. Subshells haben einige Auswirkungen, unter anderem einen Leistungsaufwand, den wir hier nicht benötigen. Weitere Informationen finden Sie im Handbuch zu Fine Bash: 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 erfordert 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 Prozess. Das bedeutet, someprog
dass die offenen Dateideskriptoren 3 und 4 übernommen werden. Wenn someprog
in Dateideskriptor 3 geschrieben wird, wird dies zum Beendigungsstatus. Der tatsächliche Beendigungsstatus wird ignoriert, da read
nur einmal gelesen wird.
Wenn Sie befürchten, dass someprog
in den Dateideskriptor 3 oder 4 geschrieben wird, sollten Sie die Dateideskriptoren vor dem Aufruf schließen someprog
.
(((((exec 3>&- 4>&-; someprog); echo $? >&3) | filter >&4) 3>&1) | (read xs; exit $xs)) 4>&1
Das exec 3>&- 4>&-
Vorher someprog
schließt den Dateideskriptor, bevor es ausgeführt wird, someprog
sodass someprog
diese Dateideskriptoren einfach nicht vorhanden sind.
Es kann auch so geschrieben werden: someprog 3>&- 4>&-
Schritt für Schritt 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:
#part3
) und rechts ( #part2
) ausgeführt. exit $xs
ist auch der letzte Befehl der Pipe und das bedeutet, dass der String von stdin der Exit-Status des gesamten Konstrukts ist.#part2
des gesamten Konstrukts hat.#part5
und #part6
) und rechts ( filter >&4
) ausgeführt. Die Ausgabe von filter
wird zu Dateideskriptor #part1
4 umgeleitet. In der Datei wurde Deskriptor 4 zu stdout umgeleitet. Dies bedeutet, dass die Ausgabe von filter
die Standardausgabe des gesamten Konstrukts ist.#part6
wird in Dateideskriptor 3 gedruckt. In #part3
Dateideskriptor 3 wurde umgeleitet #part2
. Dies bedeutet, dass der Beendigungsstatus von #part6
der endgültige Beendigungsstatus für das gesamte Konstrukt ist.someprog
ausgeführt wird. Der Exit-Status wird übernommen #part5
. Die stdout wird von der Pipe aufgenommen #part4
und an weitergeleitet filter
. Die Ausgabe von filter
wird wiederum stdout erreichen, wie in erläutert#part4
(read; exit $REPLY)
(exec 3>&- 4>&-; someprog)
vereinfacht zu someprog 3>&- 4>&-
.
{ { { { someprog 3>&- 4>&-; echo $? >&3; } | filter >&4; } 3>&1; } | { read xs; exit $xs; }; } 4>&1
Während nicht genau das, was Sie gefragt haben, könnten Sie verwenden
#!/bin/bash -o pipefail
damit Ihre Rohre die letzte Rückkehr ungleich Null zurückgeben.
könnte etwas weniger codierung sein
Edit: Beispiel
[root@localhost ~]# false | true
[root@localhost ~]# echo $?
0
[root@localhost ~]# set -o pipefail
[root@localhost ~]# false | true
[root@localhost ~]# echo $?
1
set -o pipefail
innerhalb des Skripts sollte robuster sein, zB für den Fall, dass jemand das Skript über ausführt bash foo.sh
.
-o pipefail
nicht in POSIX.
#!/bin/bash -o pipefail
. Fehler ist:/bin/bash: line 0: /bin/bash: /tmp/ff: invalid option name
#!
Linien über die ersten, und so wird dies /bin/bash
-o pipefail
/tmp/ff
anstelle der erforderlichen /bin/bash
-o
pipefail
/tmp/ff
- getopt
(oder ähnlich) Analyse der Verwendung optarg
, die in der Tagesordnung folgen ARGV
, als Argument zu -o
, so scheitert es. Wenn Sie einen Wrapper machen waren (sagen wir, bash-pf
dass gerade tat exec /bin/bash -o pipefail "$@"
, und dass auf der Put - #!
Linie, das funktionieren würde . Siehe auch: en.wikipedia.org/wiki/Shebang_%28Unix%29
Was ich mache, wenn möglich, ist, den Exit-Code von foo
in einzugeben bar
. Wenn ich zum Beispiel weiß, dass foo
niemals eine Zeile mit nur Ziffern erzeugt wird, kann ich einfach den Exit-Code eingeben:
{ foo; echo "$?"; } | awk '!/[^0-9]/ {exit($0)} {…}'
Oder wenn ich weiß, dass die Ausgabe von foo
nie eine Zeile mit nur enthält .
:
{ foo; echo .; echo "$?"; } | awk '/^\.$/ {getline; exit($0)} {…}'
Dies kann immer getan werden, wenn es eine Möglichkeit gibt, bar
alle außer der letzten Zeile zu bearbeiten und die letzte Zeile als Exit-Code zu übergeben.
Wenn bar
es sich um eine komplexe Pipeline handelt, deren Ausgabe Sie nicht benötigen, können Sie einen Teil davon umgehen, indem Sie den Beendigungscode in einem anderen Dateideskriptor drucken.
exit_codes=$({ { foo; echo foo:"$?" >&3; } |
{ bar >/dev/null; echo bar:"$?" >&3; }
} 3>&1)
Danach $exit_codes
ist es normalerweise foo:X bar:Y
, aber es könnte sein, bar:Y foo:X
dass es bar
beendet wird, bevor alle Eingaben gelesen werden, oder wenn Sie Pech haben. Ich denke, dass Schreibvorgänge in Pipes mit bis zu 512 Bytes bei allen Unices atomar sind, sodass die foo:$?
und bar:$?
-Teile nicht gemischt werden, solange die Tag-Zeichenfolgen weniger als 507 Bytes umfassen.
Wenn Sie die Ausgabe von erfassen müssen bar
, wird es schwierig. Sie können die oben genannten Techniken kombinieren, indem Sie dafür sorgen, dass die Ausgabe von bar
never eine Zeile enthält, die wie eine Exit-Code-Anzeige aussieht, die jedoch unübersichtlich 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')
Natürlich gibt es auch die einfache Möglichkeit , den Status mithilfe einer temporären Datei zu speichern. Einfach, aber nicht so einfach in der Produktion:
/tmp
ist dies der einzige Ort, an dem ein Skript Dateien schreiben kann. Verwendung mktemp
, die nicht POSIX ist, aber heutzutage auf allen seriösen 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")
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 den jeweiligen 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 $error_statuses
in meinen Tests; ohne sie grep
kann man nicht unterscheiden, weil die Zeilenumbrüche zu Leerzeichen gezwungen werden.
Ich 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:
# 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 wird am besten von innen nach außen erklärt - command1 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 seiner stdout, aber diese stdout wird umgeleitet Dateideskriptor 3.
Während Befehl1 ausgeführt wird, wird seine stdout an Befehl2 weitergeleitet (die Ausgabe von printf führt niemals zu Befehl2, da wir sie an den Dateideskriptor 3 anstelle von 1 senden, den die Pipe liest). Dann leiten wir die Ausgabe von command2 an den Dateideskriptor 4 um, so dass sie auch außerhalb des Dateideskriptors 1 bleibt - weil wir wollen, dass der Dateideskriptor 1 ein wenig später frei ist, weil wir die printf-Ausgabe des Dateideskriptors 3 wieder in den Dateideskriptor zurückführen 1 - denn genau das wird von der Befehlsersetzung (den Backticks) erfasst und in die Variable eingefügt.
Das letzte bisschen Magie ist, dass exec 4>&1
wir es zuerst als separaten Befehl gemacht haben - es öffnet den Dateideskriptor 4 als Kopie des stdout der externen Shell. Die Befehlsersetzung erfasst alles, was auf Standard geschrieben ist, aus der Perspektive der darin enthaltenen Befehle. Da die Ausgabe von Befehl2 jedoch für die Befehlsersetzung den Dateideskriptor 4 enthält, wird sie von der Befehlsersetzung nicht erfasst. Sobald die Befehlsersetzung "beendet" ist, wird der gesamte Dateideskriptor 1 des Skripts angezeigt.
(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 Befehlsersetzung zu schreiben, der in dem "externen" Befehl geöffnet wird, der die Ersetzung verwendet einfachste tragbare Möglichkeit.)
Sie können es weniger technisch und spielerisch betrachten, als ob die Ausgaben der Befehle sich gegenseitig überspringen: command1 leitet zu command2, dann springt die Ausgabe von printf über command 2, damit command2 es nicht fängt, und dann Die Ausgabe von Befehl 2 springt über die Befehlsersetzung hinaus und verlässt sie, genau wie printf gerade noch rechtzeitig landet, um von der Ersetzung erfasst zu werden, sodass sie in der Variablen endet in einer normalen Pfeife.
Soweit ich weiß, $?
wird es auch weiterhin den Rückgabecode des zweiten Befehls in der Pipe enthalten, da Variablenzuweisungen, Befehlsersetzungen und zusammengesetzte Befehle für den Rückgabecode des darin enthaltenen Befehls effektiv transparent sind, also den Rückgabestatus von befehl2 sollte weitergegeben werden - dies ist meiner meinung nach eine etwas bessere lösung als die von lesmana vorgeschlagene, da keine zusätzliche funktion definiert werden muss.
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 ( ( )
anstelle von { }
funktionieren 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 und der zusammengesetzte Befehl gefolgt von 3>&1
den Dateideskriptor drei erbt. Das 4>&-
stellt also sicher, dass der innere zusammengesetzte Befehl nicht den Dateideskriptor vier und der 3>&-
Dateideskriptor drei erbt, sodass befehl1 eine 'sauberere', standardisiertere Umgebung erhält. Sie könnten auch das Innere 4>&-
neben das bewegen 3>&-
, aber ich denke, warum nicht einfach den Umfang so weit wie möglich einschränken.
Ich bin mir nicht sicher, wie oft die Dateideskriptoren drei und vier direkt verwendet werden - ich glaube, die meisten Programme verwenden Syscalls, die im Moment nicht verwendete Dateideskriptoren zurückgeben, aber manchmal schreibt Code direkt in Dateideskriptor 3 rate (Ich könnte mir vorstellen, dass ein Programm einen Dateideskriptor überprüft, um festzustellen, ob er geöffnet ist, und ihn verwendet, wenn er geöffnet ist, oder sich dementsprechend anders verhält, wenn er nicht geöffnet ist). Letzteres ist wahrscheinlich am besten zu beachten und für allgemeine Zwecke zu verwenden.
-bash: 3: Bad file descriptor
.
Wenn Sie das Paket moreutils installiert haben, können Sie das Dienstprogramm mispipe verwenden, das genau das tut, was Sie gefragt haben.
Die obige Lösung von lesmana kann auch ohne den Mehraufwand für das Starten verschachtelter Unterprozesse mithilfe von ausgeführt werden { .. }
(wobei zu beachten ist, dass diese Form von gruppierten Befehlen immer mit Semikolons abgeschlossen werden muss). Etwas wie das:
{ { { { someprog; echo $? >&3; } | filter >&4; } 3>&1; } | stdintoexitstatus; } 4>&1
Ich habe dieses Konstrukt mit Dash-Version 0.5.5 und Bash-Versionen 3.2.25 und 4.2.42 überprüft. Selbst wenn einige Shells keine { .. }
Gruppierung unterstützen, ist es dennoch POSIX-kompatibel.
set -o pipefail
in ksh oder einer beliebigen Anzahl gestreuter wait
Befehle. Ich denke, dass es zumindest teilweise ein Parsing-Problem für ksh sein kann, als ob ich bei der Verwendung von Subshells bleibe, dann funktioniert es einwandfrei, aber selbst wenn if
man die Subshell-Variante für ksh wählt, aber die zusammengesetzten Befehle für andere belässt, schlägt dies fehl .
Dies ist portabel, funktioniert also mit jeder POSIX-kompatiblen Shell, erfordert nicht, dass das aktuelle Verzeichnis beschreibbar ist, und ermöglicht die gleichzeitige Ausführung mehrerer Skripts mit demselben Trick.
(foo;echo $?>/tmp/_$$)|(bar;exit $(cat /tmp/_$$;rm /tmp/_$$))
Edit: Hier ist eine stärkere Version, die Gilles 'Kommentaren folgt:
(s=/tmp/.$$_$RANDOM;((foo;echo $?>$s)|(bar)); exit $(cat $s;rm $s))
Edit2: und hier ist eine etwas leichtere Variante nach dubiousjim Kommentar:
(s=/tmp/.$$_$RANDOM;{foo;echo $?>$s;}|bar; exit $(cat $s;rm $s))
(s=/tmp/.$$_$RANDOM;{foo;echo $?>$s;}|bar; exit $(cat $s;rm $s))
. @Johan: Ich stimme zu, dass es mit Bash einfacher ist, aber in manchen Zusammenhängen lohnt es sich, zu wissen, wie man Bash vermeidet.
Das Folgende ist als Zusatz zur Antwort von @Patrik gedacht, falls Sie eine der gebräuchlichen Lösungen nicht verwenden können.
Diese Antwort setzt Folgendes voraus:
$PIPESTATUS
noch kenntset -o pipefail
Zusätzliche Annahmen. Sie können alles loswerden, aber dies überfrachtet das Rezept zu sehr, so dass es hier nicht behandelt wird:
- Sie möchten lediglich wissen, dass alle Befehle in der PIPE den Beendigungscode 0 haben.
- Sie benötigen keine zusätzlichen Seitenbandinformationen.
- Ihre Shell wartet darauf, dass alle Pipe-Befehle zurückgegeben werden.
Vorher: foo | bar | baz
gibt jedoch nur den Exit-Code des letzten Befehls zurück ( baz
)
Gesucht: $?
Muss nicht 0
(true) sein, wenn einer der Befehle in der Pipe fehlgeschlagen ist
Nach:
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:
mktemp
. Dadurch wird normalerweise sofort eine Datei in erstellt/tmp
wait
wird für benötigt ksh
, da ksh
sonst nicht auf den Abschluss aller Pipe-Befehle gewartet wird. Beachten Sie jedoch, dass es unerwünschte Nebeneffekte gibt, wenn Hintergrundaufgaben vorhanden sind. Daher habe ich diese standardmäßig auskommentiert. Wenn das Warten nicht schmerzt, können Sie es in kommentieren.read
zurückkehrt false
, so true
zeigt einen Fehler anDies kann als Plugin-Ersatz für einen einzelnen Befehl verwendet werden und erfordert nur Folgendes:
/proc/fd/N
BUGs:
Dieses Skript hat einen Fehler, falls /tmp
der Speicherplatz knapp wird. Wenn Sie Schutz gegen diese künstlichen Fall brauchen auch, können Sie es tun , wie folgt, was jedoch den Nachteil hat, dass die Anzahl der 0
in 000
der Anzahl von Befehlen in der Leitung hängt, so 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 Mobilität:
ksh
und ähnliche Shells, die nur auf den letzten Pipe-Befehl warten, brauchen das wait
unkommentierte
Das letzte Beispiel wird printf "%1s" "$?"
anstelle von verwendet, echo -n "$?"
da dies portabler ist. Nicht jede Plattform interpretiert -n
richtig.
printf "$?"
würde es auch 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 gerade programmieren paranoia_mode=extreme
.)
FD 8 und FD 9 können auf Plattformen, die mehrere Ziffern unterstützen, höher sein. AFAIR eine POSIX-konforme Shell muss nur einzelne Ziffern unterstützen.
War 8.2 mit Debian getestet sh
, bash
, ksh
, ash
, sash
und sogarcsh
Mit ein wenig Vorsicht sollte dies funktionieren:
foo-status=$(mktemp -t)
(foo; echo $? >$foo-status) | bar
foo_status=$(cat $foo-status)
Der folgende 'if'-Block wird nur ausgeführt, wenn' command 'erfolgreich war:
if command; then
# ...
fi
Im Einzelnen 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
Welches läuft haconf -makerw
und speichert seine stdout und stderr zu "$ haconf_out". Wenn der zurückgegebene Wert von haconf
true ist, wird der ' grep
if' -Block ausgeführt und lautet "$ haconf_out", wobei versucht wird, ihn mit "Cluster bereits beschreibbar" abzugleichen.
Beachten Sie, dass sich die Rohre automatisch selbst reinigen. 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 nicht in Reichweite ist.
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
$
(Mindestens mit bash) kann in Kombination mit set -e
subshell verwendet werden, um Pipefail explizit zu emulieren und bei einem Pipe-Fehler zu beenden
set -e
foo | bar
( exit ${PIPESTATUS[0]} )
rest of program
Wenn foo
dies 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
ein eigener Fehler ausgegeben wird, der ausreicht, um die Fehlerursache zu verstehen.)
BEARBEITEN : Diese Antwort ist falsch, aber interessant, also lasse ich sie zum späteren Nachschlagen.
!
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.
# =========================================================== #
ls
, nicht den Exit-Code von invertierenbogus_command