Ein Shell-Skript abbrechen, wenn ein Befehl einen Wert ungleich Null zurückgibt?

437

Ich habe ein Bash-Shell-Skript, das eine Reihe von Befehlen aufruft. Ich möchte, dass das Shell-Skript automatisch mit einem Rückgabewert von 1 beendet wird, wenn einer der Befehle einen Wert ungleich Null zurückgibt.

Ist dies möglich, ohne das Ergebnis jedes Befehls explizit zu überprüfen?

z.B

dosomething1
if [[ $? -ne 0 ]]; then
    exit 1
fi

dosomething2
if [[ $? -ne 0 ]]; then
    exit 1
fi
Jin Kim
quelle
8
Zusätzlich dazu set -eauch set -u(oder set -eu). -uBeendet das idiotische Verhalten, das Fehler versteckt, dass Sie auf jede nicht vorhandene Variable zugreifen können und einen leeren Wert ohne Diagnose erstellen lassen.
Kaz

Antworten:

741

Fügen Sie dies am Anfang des Skripts hinzu:

set -e

Dies führt dazu, dass die Shell sofort beendet wird, wenn ein einfacher Befehl mit einem Exit-Wert ungleich Null beendet wird. Ein einfacher Befehl ist ein Befehl, der nicht Teil eines if, while oder bis zum Test oder Teil eines && oder || ist Liste.

Siehe die bash (1) man - Seite auf dem „set“ internen Befehl für weitere Details.

Ich persönlich starte fast alle Shell-Skripte mit "set -e". Es ist wirklich ärgerlich, wenn ein Skript hartnäckig fortgesetzt wird, wenn etwas in der Mitte fehlschlägt und die Annahmen für den Rest des Skripts verletzt.

Ville Laurikari
quelle
36
Das würde funktionieren, aber ich verwende gerne "#! / Usr / bin / env bash", weil ich bash häufig von einem anderen Ort als / bin aus starte. Und "#! / Usr / bin / env bash -e" funktioniert nicht. Außerdem ist es schön, einen Ort zum Ändern zu haben, um "set -xe" zu lesen, wenn ich die Ablaufverfolgung für das Debuggen aktivieren möchte.
Ville Laurikari
48
Außerdem werden die Flags in der Shebang-Zeile ignoriert, wenn ein Skript als ausgeführt wird bash script.sh.
Tom Anderson
27
Nur eine Anmerkung: Wenn Sie Funktionen innerhalb des Bash-Skripts deklarieren, müssen die Funktionen -e im Funktionskörper neu deklariert haben, wenn Sie diese Funktionalität erweitern möchten.
Jin Kim
8
Wenn Sie Ihr Skript als Quelle verwenden, spielt die Shebang-Zeile keine Rolle.
4
@ JinKim Das scheint in Bash 3.2.48 nicht der Fall zu sein. Versuchen Sie Folgendes in einem Skript : set -e; tf() { false; }; tf; echo 'still here'. Auch ohne set -eim Körper von tf()wird die Ausführung abgebrochen. Vielleicht wollten Sie damit sagen, dass dies set -enicht von Unterschalen geerbt wird , was wahr ist.
mklement0
201

So ergänzen Sie die akzeptierte Antwort:

Denken Sie daran, dass dies set -emanchmal nicht ausreicht, insbesondere wenn Sie Rohre haben.

Angenommen, Sie haben dieses Skript

#!/bin/bash
set -e 
./configure  > configure.log
make

... was wie erwartet funktioniert: Ein Fehler in configurebricht die Ausführung ab.

Morgen nehmen Sie eine scheinbar triviale Änderung vor:

#!/bin/bash
set -e 
./configure  | tee configure.log
make

... und jetzt funktioniert es nicht. Dies wird hier erklärt und es wird eine Problemumgehung (nur Bash) bereitgestellt:

#! / bin / bash
setze -e 
setze -o Pipefail

./configure | tee configure.log
machen
leonbloy
quelle
1
Vielen Dank, dass Sie erklärt haben, wie wichtig es ist pipefail, mitmachen zu müssen set -o!
Malcolm
83

Die if-Anweisungen in Ihrem Beispiel sind nicht erforderlich. Mach es einfach so:

dosomething1 || exit 1

Wenn Sie den Rat von Ville Laurikari befolgen und set -efür einige Befehle verwenden, müssen Sie möglicherweise Folgendes verwenden:

dosomething || true

Dadurch || trueerhält die Befehlspipeline einen trueRückgabewert, auch wenn der Befehl fehlschlägt, sodass die -eOption das Skript nicht beendet.

Zan Lynx
quelle
1
Ich mag das. Vor allem, weil die Top-Antwort bash-zentriert ist (mir ist überhaupt nicht klar, ob / inwieweit sie für zsh-Scripting gilt). Und ich könnte es nachschlagen, aber deine ist nur klarer, weil Logik.
g33kz0r
set -eist nicht bash-zentriert - es wird sogar von der ursprünglichen Bourne Shell unterstützt.
Marcos Vives Del Sol
27

Wenn Sie beim Beenden eine Bereinigung durchführen müssen, können Sie auch 'trap' mit dem Pseudosignal ERR verwenden. Dies funktioniert genauso wie das Einfangen von INT oder einem anderen Signal. bash löst ERR aus, wenn ein Befehl mit einem Wert ungleich Null beendet wird:

# Create the trap with   
#    trap COMMAND SIGNAME [SIGNAME2 SIGNAME3...]
trap "rm -f /tmp/$MYTMPFILE; exit 1" ERR INT TERM
command1
command2
command3
# Partially turn off the trap.
trap - ERR
# Now a control-C will still cause cleanup, but
# a nonzero exit code won't:
ps aux | grep blahblahblah

Oder wenn Sie "set -e" verwenden, können Sie EXIT abfangen. Ihre Trap wird dann ausgeführt, wenn das Skript aus irgendeinem Grund beendet wird, einschließlich eines normalen Endes, Interrupts, eines durch die Option -e verursachten Exits usw.

fholo
quelle
12

Die $?Variable wird selten benötigt. Das Pseudo-Idiom command; if [ $? -eq 0 ]; then X; fisollte immer als geschrieben werden if command; then X; fi.

In den Fällen, in denen dies $?erforderlich ist, muss es mit mehreren Werten verglichen werden:

command
case $? in
  (0) X;;
  (1) Y;;
  (2) Z;;
esac

oder wenn $?es wiederverwendet oder anderweitig manipuliert werden muss:

if command; then
  echo "command successful" >&2
else
  ret=$?
  echo "command failed with exit code $ret" >&2
  exit $ret
fi
Mark Edgar
quelle
3
Warum sollte "immer geschrieben werden als"? Ich meine, warum " sollte " es so sein? Wenn ein Befehl lang ist (denken Sie daran, GCC mit einem Dutzend Optionen aufzurufen), ist es viel besser lesbar, den Befehl auszuführen, bevor Sie den Rückgabestatus überprüfen.
Ysap
Wenn ein Befehl zu lang ist, können Sie ihn durch Benennen aufteilen (Shell-Funktion definieren).
Mark Edgar
12

Führen Sie es mit -eoder set -eoben aus.

Schauen Sie sich auch an set -u.

Lumpynose
quelle
34
Um potenziell die Notwendigkeit speichert andere durch zu lesen help set: -ubehandelt Verweis auf nicht definierter Variablen als Fehler.
mklement0
1
Also ist es entweder set -uoder set -enicht beides? @lumpynose
Ericn
1
@eric Ich bin vor einigen Jahren in den Ruhestand gegangen. Obwohl ich meine Arbeit geliebt habe, hat mein altes Gehirn alles vergessen. Auf Anhieb würde ich vermuten, dass Sie beide zusammen verwenden könnten; schlechte Formulierung meinerseits; Ich hätte "und / oder" sagen sollen.
Lumpynose
3

Ein Ausdruck wie

dosomething1 && dosomething2 && dosomething3

beendet die Verarbeitung, wenn einer der Befehle mit einem Wert ungleich Null zurückgegeben wird. Der folgende Befehl gibt beispielsweise niemals "erledigt" aus:

cat nosuchfile && echo "done"
echo $?
1
gabor
quelle
2
#!/bin/bash -e

sollte ausreichen.

Baligh Uddin
quelle
-2

Wirf einfach eine andere als Referenz ein, da es eine zusätzliche Frage zu Mark Edgars Input gab. Hier ist ein zusätzliches Beispiel und berührt das Thema insgesamt:

[[ `cmd` ]] && echo success_else_silence

Das ist das gleiche cmd || exit errcodewie jemand gezeigt hat.

z.B. Ich möchte sicherstellen, dass eine Partition nicht gemountet ist, wenn sie gemountet ist:

[[ `mount | grep /dev/sda1` ]] && umount /dev/sda1 
Malina
quelle
5
Nein, [[ cmd`]] `ist nicht dasselbe. Es ist falsch, wenn die Ausgabe des Befehls leer und ansonsten wahr ist, unabhängig vom Beendigungsstatus des Befehls.
Gilles 'SO - hör auf böse zu sein'