Wie kann ich in einem Bash-Skript das gesamte Skript beenden, wenn eine bestimmte Bedingung auftritt?

717

Ich schreibe ein Skript in Bash, um Code zu testen. Es erscheint jedoch albern, die Tests auszuführen, wenn das Kompilieren des Codes überhaupt fehlschlägt. In diesem Fall werde ich die Tests einfach abbrechen.

Gibt es eine Möglichkeit, dies zu tun, ohne das gesamte Skript in eine while-Schleife zu packen und Pausen zu verwenden? So etwas wie ein Dun Dun Dun Goto?

Samoz
quelle

Antworten:

810

Versuchen Sie diese Aussage:

exit 1

Durch 1entsprechende Fehlercodes ersetzen . Siehe auch Exit-Codes mit besonderer Bedeutung .

Michael Foukarakis
quelle
4
@CMCDragonkai, normalerweise funktioniert jeder Code ungleich Null. Wenn Sie nichts Besonderes benötigen, können Sie es einfach 1konsequent verwenden. Wenn das Skript von einem anderen Skript ausgeführt werden soll, möchten Sie möglicherweise Ihren eigenen Satz von Statuscodes mit einer bestimmten Bedeutung definieren. Beispiel: 1== Tests fehlgeschlagen, 2== Kompilierung fehlgeschlagen. Wenn das Skript Teil von etwas anderem ist, müssen Sie möglicherweise die Codes anpassen, um sie an die dort verwendeten Praktiken anzupassen. Wenn beispielsweise ein Teil der von automake ausgeführten Testsuite verwendet wird, wird der Code 77zum Markieren eines übersprungenen Tests verwendet.
Michał Górny
21
Nein, dies schließt auch das Fenster und beendet nicht nur das Skript
Toni Leigh
7
@ToniLeigh bash hat nicht das Konzept eines "Fensters", Sie sind wahrscheinlich verwirrt darüber, was Ihre spezielle Einrichtung - z. B. ein Terminalemulator - tut.
Michael Foukarakis
3
@ToniLeigh Wenn es das "Fenster" schließt, setzen Sie den exit #Befehl wahrscheinlich in eine Funktion, nicht in ein Skript. (In diesem Fall return #stattdessen verwenden.)
Jamie
1
@Sevenearths 0 bedeutet, dass es erfolgreich war. exit 0Beenden Sie daher das Skript und geben Sie 0 zurück (und teilen Sie anderen Skripten mit, dass das Ergebnis dieses Skripts erfolgreich sein könnte)
VictorGalisson,
689

Verwenden Sie set -e

#!/bin/bash

set -e

/bin/command-that-fails
/bin/command-that-fails2

Das Skript wird nach der ersten fehlgeschlagenen Zeile beendet (gibt einen Exit-Code ungleich Null zurück). In diesem Fall wird command-that-fail2 nicht ausgeführt.

Wenn Sie den Rückgabestatus jedes einzelnen Befehls überprüfen würden, würde Ihr Skript folgendermaßen aussehen:

#!/bin/bash

# I'm assuming you're using make

cd /project-dir
make
if [[ $? -ne 0 ]] ; then
    exit 1
fi

cd /project-dir2
make
if [[ $? -ne 0 ]] ; then
    exit 1
fi

Mit set -e würde es so aussehen:

#!/bin/bash

set -e

cd /project-dir
make

cd /project-dir2
make

Jeder fehlgeschlagene Befehl führt dazu, dass das gesamte Skript fehlschlägt und einen Exit-Status zurückgibt, den Sie mit $? Überprüfen können . . Wenn Ihr Skript sehr lang ist oder Sie eine Menge Dinge erstellen, wird es ziemlich hässlich, wenn Sie überall Überprüfungen des Rückgabestatus hinzufügen.

Shizzmo
quelle
10
Mit set -eSie noch können machen einige Befehle Ausgang mit Fehlern , ohne das Skript zu stoppen: command 2>&1 || echo $?.
Adobe
6
set -ebricht das Skript ab, wenn eine Pipeline- oder Befehlsstruktur einen Wert ungleich Null zurückgibt. Zum Beispiel foo || barschlägt nur fehl, wenn beide foound einen barWert ungleich Null zurückgeben. Normalerweise funktioniert ein gut geschriebenes Bash-Skript, wenn Sie es set -ezu Beginn hinzufügen, und das Hinzufügen funktioniert als automatische Überprüfung der Integrität: Brechen Sie das Skript ab, wenn etwas schief geht.
Mikko Rantalainen
6
Wenn Sie Befehle zusammen leiten, können Sie auch fehlschlagen, wenn einer von ihnen fehlschlägt, indem Sie die set -o pipefailOption festlegen.
Jake Biesinger
18
Eigentlich wäre der idiomatische Code ohne set -egerecht make || exit $?.
Tripleee
4
Auch du hast set -u. Werfen Sie einen Blick auf den inoffiziellen Bash-Strict-Modus : set -euo pipefail.
Pablo A
236

Ein SysOps-Typ hat mir einmal die Drei-Finger-Klauen-Technik beigebracht:

yell() { echo "$0: $*" >&2; }
die() { yell "$*"; exit 111; }
try() { "$@" || die "cannot $*"; }

Diese Funktionen sind * NIX OS und Shell-Aroma-robust. Setzen Sie sie an den Anfang Ihres Skripts (Bash oder anders), try()Ihre Anweisung und Ihren Code.

Erläuterung

(basierend auf dem Kommentar eines fliegenden Schafs ).

  • yell: Drucken Sie den Skriptnamen und alle Argumente an stderr:
    • $0 ist der Pfad zum Skript;
    • $* sind alle Argumente.
    • >&2bedeutet, >stdout zu & pipe umleiten2 . Rohr1 wäre stdoutselbst.
  • diemacht das Gleiche wie yell, wird jedoch mit einem Exit-Status ungleich 0 beendet , was "fehlgeschlagen" bedeutet.
  • tryverwendet den ||(Booleschen Wert OR), der die rechte Seite nur auswertet, wenn die linke fehlgeschlagen ist.
    • $@ist alles wieder Argumente, aber anders .
c.gutierrez
quelle
3
Ich bin ziemlich neu in Unix-Skripten. Können Sie bitte erklären, wie die oben genannten Funktionen ausgeführt werden? Ich sehe eine neue Syntax, mit der ich nicht vertraut bin. Vielen Dank.
KaizenCoder
15
schreien : $0ist der Pfad zum Skript. $*sind alle Argumente. >&2bedeutet " >stdout zum &Rohr umleiten 2". Rohr 1 wäre selbst stdout. so schreien Präfixe alle Argumente mit dem Skriptnamen und Druck auf stderr. Die macht dasselbe wie schreien , beendet sich jedoch mit einem Exit-Status ungleich 0, was "fehlgeschlagen" bedeutet. try verwendet den booleschen Wert oder ||, der die rechte Seite nur auswertet, wenn die linke Seite nicht fehlgeschlagen ist. $@ist alles wieder Argumente, aber anders . hoffe, das erklärt alles
fliegende Schafe
1
Ich habe dies die() { yell "$1"; exit $2; }so geändert, dass Sie eine Nachricht übergeben und den Code mit beenden können die "divide by zero" 115.
Mark Lakata
3
Ich kann sehen , wie ich würde verwenden yellund die. Allerdings trynicht so sehr. Können Sie ein Beispiel für Ihre Verwendung angeben?
Kshenoy
2
Hmm, aber wie benutzt man sie in einem Skript? Ich verstehe nicht, was Sie unter "try () your statement and code on" verstehen.
TheJavaGuy-Ivan Milosavljević
33

Wenn Sie das Skript mit aufrufen source, können Sie verwenden, return <x>wo <x>sich der Status des Skript-Exits befindet (verwenden Sie einen Wert ungleich Null für Fehler oder Falsch). Wenn Sie jedoch ein ausführbares Skript aufrufen (dh direkt mit seinem Dateinamen), führt die return-Anweisung zu einer Beschwerde (Fehlermeldung "return: kann nur von einer Funktion oder einem Sourcing-Skript" zurückkehren ").

Wenn exit <x>stattdessen verwendet wird, wird beim Aufrufen des Skripts sourcedie Shell beendet, mit der das Skript gestartet wurde. Ein ausführbares Skript wird jedoch wie erwartet beendet.

Um beide Fälle im selben Skript zu behandeln, können Sie verwenden

return <x> 2> /dev/null || exit <x>

Dies behandelt den jeweils geeigneten Aufruf. Dies setzt voraus, dass Sie diese Anweisung auf der obersten Ebene des Skripts verwenden. Ich würde davon abraten, das Skript direkt aus einer Funktion heraus zu beenden.

Hinweis: <x>soll nur eine Zahl sein.

Kavadien
quelle
Funktioniert bei mir nicht in einem Skript mit einem Return / Exit innerhalb einer Funktion, dh es ist möglich, innerhalb der Funktion zu beenden, ohne dass die Shell vorhanden ist, ohne dass der Funktionsaufrufer für die korrekte Überprüfung des Rückkehrcodes der Funktion sorgt ?
Januar
@jan Was der Aufrufer mit Rückgabewerten macht (oder nicht macht), ist völlig orthogonal (dh unabhängig davon), wie Sie von der Funktion zurückkehren (... ohne die Shell zu verlassen, unabhängig vom Aufruf). Es hauptsächlich abhängig von dem Anrufer - Code, der nicht Teil dieser Q & A ist. Sie können den Rückgabewert der Funktion sogar an die Bedürfnisse des Anrufers
anpassen
11

Ich füge oft eine Funktion namens run () hinzu, um Fehler zu behandeln. Jeder Aufruf, den ich tätigen möchte, wird an diese Funktion übergeben, sodass das gesamte Skript beendet wird, wenn ein Fehler auftritt. Dies hat gegenüber der Lösung set -e den Vorteil, dass das Skript nicht stillschweigend beendet wird, wenn eine Zeile ausfällt, und Ihnen das Problem mitteilen kann. Im folgenden Beispiel wird die 3. Zeile nicht ausgeführt, da das Skript beim Aufruf von false beendet wird.

function run() {
  cmd_output=$(eval $1)
  return_value=$?
  if [ $return_value != 0 ]; then
    echo "Command $1 failed"
    exit -1
  else
    echo "output: $cmd_output"
    echo "Command succeeded."
  fi
  return $return_value
}
run "date"
run "false"
run "date"
Joseph Sheedy
quelle
1
Mann, aus irgendeinem Grund mag ich diese Antwort wirklich. Ich erkenne, dass es etwas komplizierter ist, aber es scheint so nützlich zu sein. Und da ich kein Bash-Experte bin, glaube ich, dass meine Logik fehlerhaft ist und dass mit dieser Methode etwas nicht stimmt. Andernfalls hätte ich das Gefühl, dass andere sie mehr gelobt hätten. Was ist das Problem mit dieser Funktion? Gibt es etwas, worauf ich hier achten sollte?
Ich erinnere mich nicht an meinen Grund für die Verwendung von eval, die Funktion funktioniert gut mit cmd_output = $ ($ 1)
Joseph Sheedy
Ich habe dies gerade als Teil eines komplexen Bereitstellungsprozesses implementiert und es hat fantastisch funktioniert. Vielen Dank und hier ist ein Kommentar und eine positive Bewertung.
Fuzzygroup
Wirklich tolle Arbeit! Dies ist die einfachste und sauberste Lösung, die gut funktioniert. Für mich habe ich dies vor einem Befehl in einer FOR-Schleife hinzugefügt, da FOR-Schleifen das nicht aufnehmen set -e option. Dann der Befehl, da es sich um Argumente handelt, habe ich einfache Anführungszeichen verwendet, um Bash-Probleme wie die folgenden zu vermeiden : runTry 'mysqldump $DB_PASS --user="$DB_USER" --host="$BV_DB_HOST" --triggers --routines --events --single-transaction --verbose $DB_SCHEMA $tables -r $BACKUP_DIR/$tables$BACKUP_FILE_NAME'. Hinweis Ich habe den Namen der Funktion in runTry geändert.
Tony-Caffe
1
evalist möglicherweise gefährlich, wenn Sie willkürliche Eingaben akzeptieren, aber ansonsten sieht das ziemlich gut aus.
Dragon788
5

Anstatt zu ifkonstruieren, können Sie die Kurzschlussbewertung nutzen :

#!/usr/bin/env bash

echo $[1+1]
echo $[2/0]              # division by 0 but execution of script proceeds
echo $[3+1]
(echo $[4/0]) || exit $? # script halted with code 1 returned from `echo`
echo $[5+1]

Beachten Sie das Klammerpaar, das aufgrund der Priorität des Wechseloperators erforderlich ist. $?ist eine spezielle Variable, die den Code des zuletzt aufgerufenen Befehls beendet.

Skalee
quelle
1
Wenn ich command -that --fails || exit $?es ohne Klammern mache echo $[4/0], warum brauchen wir sie dann?
Anentropic
2
@Anentropic @skalee Die Klammern haben nichts mit Vorrang zu tun, sondern mit Ausnahmebehandlung. Eine Division durch Null führt zu einem sofortigen Verlassen der Shell mit Code 1. Ohne die Klammern (dh eine einfache echo $[4/0] || exit $?) Bash wird niemals die ausgeführt echo, geschweige denn die ||.
Bobbogo
1

Ich habe die gleiche Frage, kann sie aber nicht stellen, da es sich um ein Duplikat handelt.

Die akzeptierte Antwort mit exit funktioniert nicht, wenn das Skript etwas komplizierter ist. Wenn Sie einen Hintergrundprozess verwenden, um nach der Bedingung zu suchen, beendet exit nur diesen Prozess, da er in einer Unter-Shell ausgeführt wird. Um das Skript zu beenden, müssen Sie es explizit beenden (zumindest ist dies der einzige Weg, den ich kenne).

Hier ist ein kleines Skript, wie es geht:

#!/bin/bash

boom() {
    while true; do sleep 1.2; echo boom; done
}

f() {
    echo Hello
    N=0
    while
        ((N++ <10))
    do
        sleep 1
        echo $N
        #        ((N > 5)) && exit 4 # does not work
        ((N > 5)) && { kill -9 $$; exit 5; } # works 
    done
}

boom &
f &

while true; do sleep 0.5; echo beep; done

Dies ist eine bessere Antwort, aber immer noch unvollständig. Ich weiß wirklich nicht, wie ich den Boom- Teil loswerden soll .

Daniel Düsentrieb
quelle