Wie kann ich das Programm beenden, wenn ein Befehl fehlgeschlagen ist?

222

Ich bin ein Neuling im Shell-Scripting. Ich möchte eine Nachricht drucken und mein Skript beenden, wenn ein Befehl fehlschlägt. Ich habe es versucht:

my_command && (echo 'my_command failed; exit)

aber es funktioniert nicht. Die Anweisungen, die dieser Zeile im Skript folgen, werden weiterhin ausgeführt. Ich benutze Ubuntu und Bash.

user459246
quelle
5
Wollten Sie, dass das nicht geschlossene Zitat ein Syntaxfehler ist, der zu Fail / Exit führen würde? Wenn nicht, sollten Sie das Zitat in Ihrem Beispiel schließen.
Kochfelder

Antworten:

406

Versuchen:

my_command || { echo 'my_command failed' ; exit 1; }

Vier Änderungen:

  • Wechseln Sie &&zu||
  • Verwenden Sie { }anstelle von( )
  • ;Nach exitund vorstellen
  • Leerzeichen nach {und vor}

Da Sie die Nachricht drucken und nur beenden möchten, wenn der Befehl fehlschlägt (wird mit einem Wert ungleich Null beendet), benötigen Sie ein ||nicht ein &&.

cmd1 && cmd2

wird ausgeführt, cmd2wenn dies cmd1erfolgreich ist (Exit-Wert 0). Wohingegen

cmd1 || cmd2

wird ausgeführt, cmd2wenn ein cmd1Fehler auftritt (Exit-Wert ungleich Null).

Wenn Sie verwenden, ( )wird der darin enthaltene Befehl in einer Unter-Shell ausgeführt. Wenn exitSie von dort aus a aufrufen , verlassen Sie die Unter-Shell und nicht Ihre ursprüngliche Shell. Daher wird die Ausführung in Ihrer ursprünglichen Shell fortgesetzt.

Um diese Verwendung zu überwinden { }

Die letzten beiden Änderungen sind für bash erforderlich.

Codaddict
quelle
4
Es scheint "umgekehrt" zu sein. Wenn eine Funktion "erfolgreich" ist, gibt sie 0 zurück, und wenn sie "fehlschlägt", gibt sie einen Wert ungleich Null zurück. Daher kann erwartet werden, dass && bewertet, wenn die erste Hälfte einen Wert ungleich Null zurückgibt. Das bedeutet nicht, dass die obige Antwort falsch ist - nein, sie ist richtig. && und || In Skripten basiert der Erfolg auf dem Erfolg und nicht auf dem Rückgabewert.
CashCow
7
Es scheint umgekehrt, aber lesen Sie es vor und es macht Sinn: "
Führen
2
Die Logik dahinter ist, dass die Sprache eine Kurzschlussauswertung (SCE) verwendet. Bei SCE hat f den Ausdruck die Form "p OR q" und p wird als wahr bewertet, dann gibt es keinen Grund, q überhaupt zu betrachten. Wenn der Ausdruck die Form "p UND q" hat und p als falsch bewertet wird, gibt es keinen Grund, q zu betrachten. Der Grund dafür ist zweierlei: 1) Es ist aus offensichtlichen Gründen schneller und 2) es vermeidet bestimmte Arten von Fehlern (zum Beispiel: "wenn x! = 0 UND 10 / x> 5" stürzt ab, wenn keine SCE vorhanden ist ). Die Möglichkeit, es in der Befehlszeile wie diese zu verwenden, ist ein erfreulicher Nebeneffekt.
user2635263
2
Ich würde empfehlen, STDERR zu wiederholen:{ (>&2 echo 'my_command failed') ; exit 1; }
Rynop
127

Die anderen Antworten haben die direkte Frage gut abgedeckt, aber Sie könnten auch daran interessiert sein, sie zu verwenden set -e. Bei jedem fehlgeschlagenen Befehl (außerhalb bestimmter Kontexte wie ifTests) wird das Skript abgebrochen. Für bestimmte Skripte ist es sehr nützlich.

Daenyth
quelle
3
Leider ist es auch sehr gefährlich - es set -egibt ein langes und arkanes Regelwerk darüber, wann es funktioniert und wann nicht. (Wenn jemand ausgeführt wird if yourfunction; then ..., set -ewird er niemals im Inneren yourfunctionoder in einem von ihm aufgerufenen Objekt ausgelöst, da dieser Code als "aktiviert" betrachtet wird.) Lesen Sie den Übungsabschnitt von BashFAQ # 105 , in dem diese und andere Fallstricke erläutert werden (von denen einige nur für bestimmte Shell-Versionen gelten. Sie müssen also sicherstellen, dass Sie gegen jede Version testen, die zum Ausführen Ihres Skripts verwendet werden kann).
Charles Duffy
67

Beachten Sie auch, dass der Exit-Status jedes Befehls in der Shell-Variablen $? Gespeichert ist, die Sie unmittelbar nach dem Ausführen des Befehls überprüfen können. Ein Status ungleich Null zeigt einen Fehler an:

my_command
if [ $? -eq 0 ]
then
    echo "it worked"
else
    echo "it failed"
fi
Alex Howansky
quelle
10
Dies kann nur durch if my_command ersetzt werden. Hier muss der Befehl test nicht verwendet werden.
Bart Sas
5
+1, weil ich denke, Sie sollten nicht dafür bestraft werden, dass Sie diese Alternative aufgelistet haben - sie sollte auf dem Tisch liegen -, obwohl sie irgendwie hässlich und meiner Erfahrung nach zu einfach ist, einen anderen Befehl dazwischen auszuführen, ohne die Art des Tests zu bemerken (vielleicht habe ich ' Ich bin nur dumm).
Tony Delroy
1
@BartSas Wenn der Befehl lang ist, ist es besser, ihn in eine eigene Zeile zu setzen. Dadurch wird das Skript besser lesbar.
Michael
@Michael, nicht wenn es Abhängigkeiten über Zeilen hinweg erzeugt, bei denen Sie wissen müssen, was in Zeile A passiert ist, bevor Sie Zeile B verstehen können (oder, schlimmer noch, müssen Sie wissen, was in Zeile B passieren wird, bevor Sie wissen, dass es sicher ist, etwas hinzuzufügen neu nach dem Ende von Zeile A). Ich habe zu oft gesehen echo "Just finished step A", dass jemand ein hinzugefügt hat , ohne zu wissen, dass dies das echoüberschrieb $?, was später überprüft werden sollte.
Charles Duffy
67

Wenn Sie dieses Verhalten für alle Befehle in Ihrem Skript wünschen, fügen Sie einfach hinzu

  set -e 
  set -o pipefail

am Anfang des Skripts. Dieses Optionspaar weist den Bash-Interpreter an, das Programm zu beenden, wenn ein Befehl mit einem Exit-Code ungleich Null zurückgegeben wird.

Auf diese Weise können Sie jedoch keine Beendigungsnachricht drucken.

damienfrancois
quelle
8
Sie können Befehle beim Beenden mit dem trapintegrierten Befehl bash ausführen .
Gavin Smith
Dies ist die Antwort auf die XY-Frage.
JWG
5
Es könnte interessant sein zu erklären, was Befehle unabhängig vom Paar tun. Zumal set -o pipefailmöglicherweise nicht das gewünschte Verhalten vorliegt. Trotzdem danke für den Hinweis!
Antoine Pinsard
3
set -eist überhaupt nicht zuverlässig, konsistent oder vorhersehbar! Um sich davon zu überzeugen, lesen Sie den Übungsabschnitt von BashFAQ # 105 oder die Tabelle, in der das Verhalten verschiedener Muscheln unter inulm.de/~mascheck/various/set-e
Charles Duffy am
14

Ich habe die folgende Redewendung gehackt:

echo "Generating from IDL..."
idlj -fclient -td java/src echo.idl
if [ $? -ne 0 ]; then { echo "Failed, aborting." ; exit 1; } fi

echo "Compiling classes..."
javac *java
if [ $? -ne 0 ]; then { echo "Failed, aborting." ; exit 1; } fi

echo "Done."

Stellen Sie jedem Befehl ein informatives Echo voran und folgen Sie jedem Befehl mit derselben
if [ $? -ne 0 ];...Zeile. (Natürlich können Sie diese Fehlermeldung bearbeiten, wenn Sie möchten.)

Grant Birchmeier
quelle
Dies könnte als Funktion definiert werden und somit wiederverwendet werden.
Eric Wang
1
'wenn [$? -ne 0]; dann ... fi 'ist eine komplizierte Art,' || 'zu schreiben.
Kevin Cline
if ! javac *.java; then ...- warum $?überhaupt?
Charles Duffy
Charles - weil ich bestenfalls ein mittelmäßiger Bash-Scripter bin :)
Grant Birchmeier
10

Vorausgesetzt my_commandist kanonisch gestaltet, dh gibt 0 zurück, wenn dies erfolgreich ist, und &&ist dann genau das Gegenteil von dem, was Sie wollen. Du willst ||.

Beachten Sie auch, dass (mir das in Bash nicht richtig erscheint, aber ich kann nicht versuchen, wo ich bin. Sag mir.

my_command || {
    echo 'my_command failed' ;
    exit 1; 
}
Benoit
quelle
IIRC, Sie benötigen Semikolons nach jeder der Zeilen in {}
Alex Howansky
9
@ Alex: nicht wenn sie in einer separaten Leitung sind.
Bis auf weiteres angehalten.
5

Sie können auch Folgendes verwenden, wenn Sie den Status des Beendigungsfehlers beibehalten und eine lesbare Datei mit einem Befehl pro Zeile haben möchten:

my_command1 || exit $?
my_command2 || exit $?

Dadurch wird jedoch keine zusätzliche Fehlermeldung ausgegeben. In einigen Fällen wird der Fehler jedoch trotzdem durch den fehlgeschlagenen Befehl gedruckt.

Alexpirin
quelle
1
Es gibt keinen Grund dafür exit $?; nur exitverwendet $?als seinen Standardwert.
Charles Duffy
@ CharlesDuffy wusste es nicht. Genial, also noch einfacher!
Alexpirine
3

Die integrierte trapShell ermöglicht das Abfangen von Signalen und anderen nützlichen Bedingungen, einschließlich fehlgeschlagener Befehlsausführung (dh eines Rückgabestatus ungleich Null). Wenn Sie also nicht den Rückgabestatus jedes einzelnen Befehls explizit testen möchten, können Sie sagen, dass trap "your shell code" ERRder Shell-Code jedes Mal ausgeführt wird, wenn ein Befehl einen Status ungleich Null zurückgibt. Beispielsweise:

trap "echo script failed; exit 1" ERR

Beachten Sie, dass Pipelines wie in anderen Fällen, in denen fehlgeschlagene Befehle abgefangen werden, eine besondere Behandlung benötigen. das oben genannte wird nicht fangen false | true.

Kozel
quelle