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?


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.


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.


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.


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 $?.


25
[ $? -eq 0 ] || exit $?; # exit for none-zero return code

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 .


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;
}

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

+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

2
Es gibt selten einen Grund zu verwenden $*; Verwenden Sie "$@"stattdessen, um Leerzeichen und Platzhalter zu erhalten.
— Davis Herring
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.