Wie erhalte ich die Ausgabe und den Exit-Wert einer Subshell bei Verwendung von "bash -e"?

70

Betrachten Sie den folgenden Code

Outer-scope.sh

#!/bin/bash
set -e
source inner-scope.sh
echo $(inner)
echo "I thought I would've died :("

inner-scope.sh

#!/bin/bash
function inner() { echo "winner"; return 1; }

Ich versuche zu beenden outer-scope.sh, wenn ein Anruf inner()fehlschlägt. Da $()eine Sub-Shell aufgerufen wird, geschieht dies nicht.

Wie kann ich die Ausgabe einer Funktion erhalten, ohne die Tatsache zu vernachlässigen, dass die Funktion mit einem Exit-Code ungleich Null beendet wird?

Jabalsad
quelle

Antworten:

102

$()behält den Ausgangsstatus bei; Sie müssen es nur in einer Anweisung verwenden, die keinen eigenen Status hat, z. B. in einer Zuweisung.

output = $ (inner)

Danach $?würde der Exit-Status von enthalten inner, und Sie können alle Arten von Prüfungen dafür verwenden:

output=$(inner) || exit $?
echo $output

Oder:

if ! output=$(inner); then
    exit $?
fi
echo $output

Oder:

if output=$(inner); then
    echo $output
else
    exit $?
fi

(Hinweis: Ein Bare exitohne Argumente entspricht exit $?- das heißt, es wird mit dem Beendigungsstatus des letzten Befehls beendet. Ich habe die zweite Form nur aus Gründen der Klarheit verwendet.)


Auch für die Aufzeichnung: sourceist in diesem Fall völlig unabhängig. Sie können nur inner()in der outer-scope.shDatei definieren , mit den gleichen Ergebnissen.

Grawity
quelle
Warum ist es das, obwohl $? enthält den Exit-Status von $ (), den das Skript nicht automatisch beendet (vorausgesetzt, -e ist gesetzt)? EDIT: egal, ich denke du hast meine Fragen beantwortet, danke!
Jabalsad
Ich bin mir nicht sicher. (Ich habe keines der oben genannten Tests durchgeführt.) Aber es gibt einige Einschränkungen für -e, die alle in der Manpage von bash erläutert werden. Wenn Sie nachfragen echo $(), kann dies auch daran liegen, dass die Exit-Codes der Subshells ignoriert werden, wenn die Zeile - der echoBefehl - einen eigenen Exit-Code (normalerweise 0) hat.
Grawity
1
Hmm, wenn ich tippe, if ! $(exit 1) ; then echo $?; fibekomme ich 0. Sie sind sich nicht sicher if, ob Sie diesen Exit-Wert beibehalten müssen.
Ron Burk
@grawity - Funktioniert das mit Dash? Ich versuche, ein nerviges Autoconf-Verhalten zu umgehen (nämlich, dass Autoconf einen Erfolg meldet, wenn Sun oder der IBM-Compiler eine unzulässige Option auf dem Terminal ausgibt).
Jww
3
if ! output=$(inner); then exit $?; fiwird mit einem Rückgabewert von 0 beendet, da $?der Rückgabewert von !anstelle des Rückgabewerts von angegeben wird inner. Sie könnten das gewünschte Verhalten mit bekommen, if output=$(inner); then : ; else exit $?; fiaber das ist offensichtlich ausführlicher
SJL
26

Siehe BashFAQ / 002 :

Wenn Sie beides möchten (Ausgabe- und Beendigungsstatus):

output=$(command)
status=$? 

Ein besonderer Fall

Beachten Sie bei einem schwierigen Fall mit lokalen Funktionsvariablen den folgenden Code:

f() { local    v=$(echo data; false); echo output:$v, status:$?; }
g() { local v; v=$(echo data; false); echo output:$v, status:$?; }

Wir werden .. bekommen:

$ f     # fooled by 'local' with inline initialization
output:data, status:0

$ g     # a good one
output:data, status:1

Warum?

Wenn die Ausgabe einer Subshell zum Initialisieren einer localVariablen verwendet wird, ist der Beendigungsstatus nicht mehr die Subshell, sondern der wahrscheinlichste Befehl .local 0

Siehe auch https://stackoverflow.com/a/4421282/537554

ryenus
quelle
3
Obwohl dies die Frage nicht wirklich beantwortete, kam mir dies heute zugute, also +1.
Vormittags,
1
Der Exit-Status für Bash-Befehle ist immer der des zuletzt ausgeführten Befehls. Wenn wir so viel Zeit in stark typisierten Sprachen verbringen, kann man leicht vergessen, dass "local" kein Typbezeichner ist, sondern nur ein weiterer Befehl. Vielen Dank für die Wiederholung dieses Punktes hier, hat mir heute geholfen.
Markeissler
2
Wow, ich bin gerade auf genau dieses Problem gestoßen und du hast es geklärt. Vielen Dank!
krb686
4
#!/bin/bash
set -e
source inner-scope.sh
foo=$(inner)
echo $foo
echo "I thought I would've died :("

Durch das Hinzufügen echosteht die Subshell nicht alleine (wird nicht separat geprüft) und bricht nicht ab. Die Zuordnung umgeht dieses Problem.

Sie können dies auch tun und die Ausgabe in eine Datei umleiten, um sie später zu verarbeiten.

tmpfile=$( mktemp )
inner > $tmpfile
cat $tmpfile
rm $tmpfile
Daniel Beck
quelle
Natürlich existiert die $ tmpfile in der zweiten Variante weiterhin ...
Daniel Beck