Beenden Sie das Shell-Skript basierend auf dem Prozess-Exit-Code

378

Ich habe ein Shell-Skript, das eine Reihe von Befehlen ausführt. Wie kann ich das Shell-Skript beenden, wenn einer der Befehle mit einem Exit-Code ungleich Null beendet wird?

Mark Roddy
quelle
3
Ich habe angenommen, dass Sie bash verwenden, aber wenn es sich um eine ganz andere Shell handelt, können Sie dies in Ihrem Beitrag angeben?
Martin W
Harte Methode: Testen Sie den Wert von $?nach jedem Befehl. Einfache Methode: Setzen Sie set -eoder ganz #!/bin/bash -eoben in Ihr Bash-Skript.
mwfearnley

Antworten:

494

Nach jedem Befehl befindet sich der Exit-Code in der $?Variablen, sodass Sie Folgendes haben:

ls -al file.ext
rc=$?; if [[ $rc != 0 ]]; then exit $rc; fi

Sie müssen vorsichtig mit Piped-Befehlen sein, da der $?einzige nur den Rückkehrcode des letzten Elements in der Pipe enthält, also im Code:

ls -al file.ext | sed 's/^/xx: /"

gibt keinen Fehlercode zurück, wenn die Datei nicht vorhanden ist (da der sedTeil der Pipeline tatsächlich funktioniert und 0 zurückgibt).

Die bashShell stellt tatsächlich ein Array bereit, das in diesem Fall hilfreich sein kann PIPESTATUS. Dieses Array verfügt über ein Element für jede Pipeline-Komponente, auf das Sie einzeln zugreifen können ${PIPESTATUS[0]}:

pax> false | true ; echo ${PIPESTATUS[0]}
1

Beachten Sie, dass Sie damit das Ergebnis des falseBefehls erhalten, nicht die gesamte Pipeline. Sie können auch die gesamte Liste nach Belieben verarbeiten lassen:

pax> false | true | false; echo ${PIPESTATUS[*]}
1 0 1

Wenn Sie den größten Fehlercode aus einer Pipeline erhalten möchten, können Sie Folgendes verwenden:

true | true | false | true | false
rcs=${PIPESTATUS[*]}; rc=0; for i in ${rcs}; do rc=$(($i > $rc ? $i : $rc)); done
echo $rc

Dies geht nacheinander durch jedes der PIPESTATUSElemente und speichert es, rcwenn es größer als der vorherige rcWert war.

paxdiablo
quelle
39
Gleiche Funktion in nur einer Zeile des tragbaren Codes: ls -al file.ext || exit $?([[]] ist nicht tragbar)
MarcH
19
MarcH, ich denke, Sie werden feststellen, dass [[ ]]das ziemlich portabel bashist, was die Frage ist :-) Seltsamerweise lsfunktioniert es nicht, command.comalso ist es auch nicht portabel, ich weiß, es ist die gleiche Art von Argument, die Sie haben vorhanden.
Paxdiablo
39
Ich weiß, dass dies uralt ist, aber es sollte beachtet werden, dass Sie den Exit-Code von Befehlen in einer Pipe über das Array PIPESTATUS${PIPESTATUS[0]}${PIPESTATUS[1]}${PIPESTATUS[*]}
abrufen können
11
Es muss betont werden, dass elegantes und idiomatisches Shell-Scripting sehr selten $?direkt untersucht werden muss. Normalerweise möchten Sie so etwas wie das, if ls -al file.ext; then : nothing; else exit $?; fiwas natürlich wie @MarcH sagt, äquivalent ist, ls -al file.ext || exit $?aber wenn die Klauseln thenoder elseetwas komplexer sind, ist es wartbarer.
Tripleee
9
[[ $rc != 0 ]]wird Ihnen einen 0: not foundoder 1: not foundFehler geben. Dies sollte in geändert werden [ $rc -ne 0 ]. Auch rc=$?könnte dann entfernt und einfach verwendet werden [ $? -ne 0 ].
CurtisLeeBolin
223

Wenn Sie mit $? Arbeiten möchten, müssen Sie dies nach jedem Befehl überprüfen, da $? wird nach jedem Befehlsausgang aktualisiert. Dies bedeutet, dass Sie beim Ausführen einer Pipeline nur den Exit-Code des letzten Prozesses in der Pipeline erhalten.

Ein anderer Ansatz besteht darin, dies zu tun:

set -e
set -o pipefail

Wenn Sie dies oben in das Shell-Skript einfügen, wird Bash dies anscheinend für Sie erledigen. Wie in einem vorherigen Poster erwähnt, führt "set -e" dazu, dass bash bei einem einfachen Befehl mit einem Fehler beendet wird. "set -o pipefail" führt dazu, dass bash auch bei einem Befehl in einer Pipeline mit einem Fehler beendet wird.

Weitere Informationen zu diesem Problem finden Sie hier oder hier . Hier ist der Abschnitt mit dem Bash-Handbuch zum eingebauten Set.

Jeff Hill
quelle
6
Dies sollte wirklich die beste Antwort sein: Es ist viel, viel einfacher, dies zu tun, als PIPESTATUSüberall Exit-Codes zu verwenden und zu überprüfen.
Candu
2
#!/bin/bash -eist die einzige Möglichkeit, ein Shell-Skript zu starten. Sie können immer Dinge verwenden, z. B. foo || handle_error $?wenn Sie den Exit-Status tatsächlich überprüfen müssen.
Davis Herring
53

" set -e" ist wahrscheinlich der einfachste Weg, dies zu tun. Stellen Sie das einfach vor die Befehle in Ihrem Programm.

Allen
quelle
6
@SwaroopCH set -eIhr Skript wird abgebrochen , wenn ein Befehl in Ihrem Skript mit dem Fehlerstatus beendet wird und Sie diesen Fehler nicht behandelt haben.
Andrew
2
set -eist 100% äquivalent, zu set -o errexitdem im Gegensatz zu ersteren gesucht werden kann. Suche nach opengroup + errexit für offizielle Dokumentation.
MarcH
30

Wenn Sie in der Bash nur exit ohne Parameter aufrufen, wird der Exit-Code des letzten Befehls zurückgegeben. In Kombination mit OR sollte die Bash nur dann exit aufrufen, wenn der vorherige Befehl fehlschlägt. Aber ich habe das nicht getestet.

Befehl1 || Ausfahrt;
Befehl2 || Ausfahrt;

Der Bash speichert auch den Exit-Code des letzten Befehls in der Variablen $?.

Arvodan
quelle
25
[ $? -eq 0 ] || exit $?; # exit for none-zero return code
chemila
quelle
3
Sollte das nicht sein exit $?(keine Eltern)?
Malthe
21

http://cfaj.freeshell.org/shell/cus-faq-2.html#11

  1. Wie bekomme ich den Exit-Code von cmd1incmd1|cmd2

    Beachten Sie zunächst, dass der cmd1Exit-Code ungleich Null sein kann und dennoch keinen Fehler bedeutet. Dies geschieht zum Beispiel in

    cmd | head -1

    Möglicherweise wird ein Exit-Status von 141 (oder 269 mit ksh93) von beobachtet cmd1, der jedoch cmddurch ein SIGPIPE-Signal unterbrochen wurde, als head -1er nach dem Lesen einer Zeile beendet wurde.

    Den Exit-Status der Elemente einer Pipeline kennen cmd1 | cmd2 | cmd3

    ein. mit zsh:

    Die Exit-Codes sind im speziellen Array pipestatus enthalten. cmd1Exit-Code ist in $pipestatus[1], cmd3Exit-Code in $pipestatus[3], so dass $?das immer das gleiche ist wie $pipestatus[-1].

    b. mit bash:

    Die Exit-Codes werden im PIPESTATUSspeziellen Array bereitgestellt . cmd1Exit-Code ist in ${PIPESTATUS[0]}, cmd3Exit-Code in ${PIPESTATUS[2]}, so dass $?das immer das gleiche ist wie ${PIPESTATUS: -1}.

    ...

    Weitere Details finden Sie unter folgendem Link .

gatoatigrado
quelle
19

für Bash:

# this will trap any errors or commands with non-zero exit status
# by calling function catch_errors()
trap catch_errors ERR;

#
# ... the rest of the script goes here
#  

function catch_errors() {
   # do whatever on errors
   # 
   #
   echo "script aborted, because of errors";
   exit 0;
}

quelle
19
Wahrscheinlich sollte "0 nicht beenden", da dies auf Erfolg hinweist.
Nobar
3
exit_code = $?; echo "Skript wegen Fehlern abgebrochen"; exit $ exit_code
RaSergiy
1
@ HAL9001 hast du Beweise dafür? In der IBM Dokumentation ist etwas anderes angegeben .
Patrick James McDougle
11

In Bash ist das einfach, binde sie einfach mit && zusammen:

command1 && command2 && command3

Sie können auch das verschachtelte if-Konstrukt verwenden:

if command1
   then
       if command2
           then
               do_something
           else
               exit
       fi
   else
       exit
fi
Martin W.
quelle
+1 Dies war die einfachste Lösung, nach der ich gesucht habe. Darüber hinaus können Sie auch schreiben, if (! command)wenn Sie vom Befehl einen Fehlercode ungleich Null erwarten.
Berci
Dies ist für sequenzielle Befehle. Was ist, wenn ich diese 3 parallel starten und alle töten möchte, wenn einer von ihnen ausfällt?
Vasile Surdu
4
#
#------------------------------------------------------------------------------
# run a command on failure exit with message
# doPrintHelp: doRunCmdOrExit "$cmd"
# call by:
# set -e ; doRunCmdOrExit "$cmd" ; set +e
#------------------------------------------------------------------------------
doRunCmdOrExit(){
    cmd="$@" ;

    doLog "DEBUG running cmd or exit: \"$cmd\""
    msg=$($cmd 2>&1)
    export exit_code=$?

    # if occured during the execution exit with error
    error_msg="Failed to run the command:
        \"$cmd\" with the output:
        \"$msg\" !!!"

    if [ $exit_code -ne 0 ] ; then
        doLog "ERROR $msg"
        doLog "FATAL $msg"
        doExit "$exit_code" "$error_msg"
    else
        #if no errors occured just log the message
        doLog "DEBUG : cmdoutput : \"$msg\""
        doLog "INFO  $msg"
    fi

}
#eof func doRunCmdOrExit
Yordan Georgiev
quelle
2
Es gibt selten einen Grund zu verwenden $*; Verwenden Sie "$@"stattdessen, um Leerzeichen und Platzhalter zu erhalten.
Davis Herring