it-swarm.com.de

Fehler in einem Bash-Skript auslösen

Ich möchte einen Fehler in einem Bash-Skript mit der Meldung "Testfälle fehlgeschlagen !!!" ausgeben. Wie mache ich das in Bash?

Zum Beispiel:

if [ condition ]; then
    raise error "Test cases failed !!!"
fi
59
user3078630

Dies hängt davon ab, wo die Fehlermeldung gespeichert werden soll. 

Sie können Folgendes tun:

echo "Error!" > logfile.log
exit 125

Oder das Folgende:

echo "Error!" 1>&2
exit 64

Wenn Sie eine Ausnahme auslösen, beenden Sie die Ausführung des Programms. 

Sie können auch etwas wie exit xxx verwenden, wobei xxx der Fehlercode ist, den Sie an das Betriebssystem zurückgeben möchten (von 0 bis 255). Hier sind 125 und 64 nur Zufallscodes, mit denen Sie den Vorgang beenden können. Wenn Sie dem Betriebssystem mitteilen müssen, dass das Programm abnormal angehalten hat (z. B. ein Fehler aufgetreten ist), müssen Sie einen non-zero-Exit-Code an exit übergeben. 

Wie @chepner wies auf hin, können Sie exit 1 ausführen, was einen nicht angegebenen Fehler bedeutet. 

77
ForceBru

Grundlegende Fehlerbehandlung

Wenn Ihr Testfallläufer einen Nicht-Null-Code für fehlgeschlagene Tests zurückgibt, können Sie einfach Folgendes schreiben:

test_handler test_case_x; test_result=$?
if ((test_result != 0)); then
  printf '%s\n' "Test case x failed" >&2  # write error message to stderr
  exit 1                                  # or exit $test_result
fi

Oder noch kürzer:

if ! test_handler test_case_x; then
  printf '%s\n' "Test case x failed" >&2
  exit 1
fi

Oder die kürzeste:

test_handler test_case_x || { printf '%s\n' "Test case x failed" >&2; exit 1; }

So beenden Sie den Exit-Code von test_handler:

test_handler test_case_x || { ec=$?; printf '%s\n' "Test case x failed" >&2; exit $ec; }

Erweiterte Fehlerbehandlung

Wenn Sie einen umfassenderen Ansatz verfolgen möchten, können Sie einen Fehlerbehandler verwenden:

exit_if_error() {
  local exit_code=$1
  shift
  [[ $exit_code ]] &&               # do nothing if no error code passed
    ((exit_code != 0)) && {         # do nothing if error code is 0
      printf 'ERROR: %s\n' "[email protected]" >&2 # we can use better logging here
      exit "$exit_code"             # we could also check to make sure
                                    # error code is numeric when passed
    }
}

dann rufen Sie es auf, nachdem Sie Ihren Testfall ausgeführt haben:

run_test_case test_case_x
exit_if_error $? "Test case x failed"

oder 

run_test_case test_case_x || exit_if_error $? "Test case x failed"

Die Vorteile eines Fehlerhandlers wie exit_if_error sind:

  • wir können die gesamte Fehlerbehandlungslogik standardisieren, z. B. Protokollierung , Drucken eines Stapelablaufverfolgung , Benachrichtigung, Bereinigung usw. an einer Stelle
  • indem der Fehlerbehandler den Fehlercode als Argument erhält, können wir den Aufrufer vor dem Durcheinander von if-Blöcken schützen, die Exitcodes auf Fehler testen
  • wenn wir einen Signal-Handler haben (mit trap ), können wir den Fehler-Handler von dort aus aufrufen

Zusammenhängende Posts

16
codeforester

Es gibt mehrere Möglichkeiten, wie Sie dieses Problem angehen können. Angenommen, Sie müssen ein Shell-Skript/eine Shell-Funktion mit einigen Shell-Befehlen ausführen und prüfen, ob das Skript erfolgreich ausgeführt wurde, und im Fehlerfall Fehler ausgeben. 

Die Shell-Befehle verlassen sich im Allgemeinen auf zurückgegebene Exit-Codes, um der Shell mitzuteilen, ob sie erfolgreich war oder aufgrund unerwarteter Ereignisse fehlgeschlagen ist. 

Was Sie also tun möchten, fällt auf diese beiden Kategorien

  • bei Fehler beenden
  • beenden und Bereinigung bei Fehler

Abhängig von der gewünschten Option stehen Shell-Optionen zur Verfügung. Für den ersten Fall bietet die Shell eine Option mit set -e und für den zweiten Fall können Sie trap für EXIT ausführen.

Soll ich exit in meinem Skript/in meiner Funktion verwenden?

Die Verwendung von exit verbessert im Allgemeinen die Lesbarkeit In bestimmten Routinen möchten Sie, sobald Sie die Antwort kennen, sofort zur aufrufenden Routine wechseln. Wenn die Routine so definiert ist, dass nach dem Auftreten eines Fehlers keine weitere Bereinigung erforderlich ist, wird das sofortige Beenden nicht sofort beendet, wenn Sie mehr Code schreiben müssen.

In Fällen, in denen Sie Bereinigungsaktionen für ein Skript durchführen müssen, um die Beendigung des Skripts zu bereinigen, ist not vorzugsweise exit zu verwenden. 

Sollte ich set -e für Fehler beim Beenden verwenden?

Nein!

set -e war ein Versuch, der Shell "automatische Fehlererkennung" hinzuzufügen. Sein Ziel war es, die Shell bei jedem Fehler abzubrechen, aber es gibt viele mögliche Fallstricke. 

  • Die Befehle, die Teil eines if-Tests sind, sind immun. Wenn Sie im Beispiel erwarten, dass bei der test-Überprüfung des nicht vorhandenen Verzeichnisses ein Fehler auftritt, wird dies nicht der Fall sein, sondern die Bedingung else

    set -e
    f() { test -d nosuchdir && echo no dir; }
    f
    echo survived
    
  • Befehle in einer anderen als der letzten Pipeline sind immun. Im folgenden Beispiel wird der Exit-Code des zuletzt ausgeführten Befehls (cat) als der zuletzt ausgeführte Befehl betrachtet und war erfolgreich. Dies könnte durch die Einstellung der Option set -o pipefail vermieden werden, ist jedoch immer noch ein Nachteil. 

    set -e
    somecommand that fails | cat -
    echo survived 
    

Empfohlen für den Einsatz - trap beim Beenden

Das Ergebnis ist, wenn Sie einen Fehler behandeln können, anstatt blind zu beenden, anstatt set -e zu verwenden, verwenden Sie trap für das ERR-Pseudosignal.

Die ERR-Falle soll keinen Code ausführen, wenn die Shell selbst mit einem von Null verschiedenen Fehlercode beendet wird, aber wenn ein Befehl von dieser Shell ausgeführt wird, der nicht Teil einer Bedingung ist (wie in, wenn cmd oder cmd ||) Zero Exit Status.

Im Allgemeinen definieren wir einen Trap-Handler, der zusätzliche Debug-Informationen zu der Zeile und zu der Ursache des Exits bereitstellt. Denken Sie daran, dass der Beendigungscode des letzten Befehls, der das ERR-Signal verursacht hat, zu diesem Zeitpunkt noch verfügbar ist.

cleanup() {
    exitcode=$?
    printf 'error condition hit\n' 1>&2
    printf 'exit code returned: %s\n' "$exitcode"
    printf 'the command executing at the time of the error was: %s\n' "$BASH_COMMAND"
    printf 'command present on line: %d' "${BASH_LINENO[0]}"
    # Some more clean up code can be added here before exiting
    exit $exitcode
}

und wir verwenden diesen Handler wie folgt über dem fehlgeschlagenen Skript

trap cleanup ERR

Wenn Sie dies in einem einfachen Skript zusammenfassen, das false in Zeile 15 enthielt, die Informationen, die Sie erhalten würden

error condition hit
exit code returned: 1
the command executing at the time of the error was: false
command present on line: 15

trap bietet unabhängig vom Fehler auch Optionen, um die Bereinigung nach Abschluss der Shell-Aktion auszuführen (z. B. wenn Ihr Shell-Skript beendet wird), und zwar mit dem Signal EXIT. Sie können auch mehrere Signale gleichzeitig einfangen. Die Liste der unterstützten Signale zum Trap-On finden Sie auf der trap.1p - Linux-Manualpage.

Eine weitere Sache, die Sie beachten sollten, ist zu verstehen, dass keine der bereitgestellten Methoden funktioniert, wenn Sie Sub-Shells verwenden. In diesem Fall müssen Sie möglicherweise Ihre eigene Fehlerbehandlung hinzufügen.

  • Auf einer Sub-Shell mit set -e würde das nicht funktionieren. false ist auf die Sub-Shell beschränkt und wird niemals an die übergeordnete Shell weitergegeben. Fügen Sie zur Fehlerbehandlung hier Ihre eigene Logik hinzu, um (false) || false zu tun.

    set -e
    (false)
    echo survived
    
  • Dasselbe passiert auch mit trap. Die Logik unten würde aus den oben genannten Gründen nicht funktionieren.

    trap 'echo error' ERR
    (false)
    
5
Inian

Hier ist ein einfacher Trap, der das letzte Argument des fehlgeschlagenen Befehls an STDERR ausgibt, die Zeile angibt, in der der Fehler aufgetreten ist, und das Skript mit der Zeilennummer als Beendigungscode beendet. Beachten Sie, dass dies nicht immer großartige Ideen sind, aber dies zeigt eine kreative Anwendung, auf der Sie aufbauen können.

trap 'echo >&2 "$_ at $LINENO"; exit $LINENO;' ERR

Ich habe das in ein Skript mit einer Schleife geschrieben, um es zu testen. Ich suche nur nach einem Treffer bei einigen Zufallszahlen. Sie könnten tatsächliche Tests verwenden. Wenn ich eine Kaution aufbringen muss, rufe ich mit der Nachricht, die ich werfen möchte, False (was die Falle auslöst) an.

Lassen Sie den Trap für eine ausgereifte Funktionalität eine Verarbeitungsfunktion aufrufen. Sie können immer eine case-Anweisung für Ihr Argument ($ _) verwenden, wenn Sie weitere Aufräumarbeiten durchführen müssen. Zuweisen einer var für etwas syntaktischen Zucker -

trap 'echo >&2 "$_ at $LINENO"; exit $LINENO;' ERR
throw=false
raise=false

while :
do x=$(( $RANDOM % 10 ))
   case $x in
   0) $throw "DIVISION BY ZERO" ;;
   3) $raise "MAGIC NUMBER"     ;;
   *) echo got $x               ;;
   esac
done

Beispielausgabe:

# bash tst
got 2
got 8
DIVISION BY ZERO at 6
# echo $?
6

Natürlich könntest du 

runTest1 "Test1 fails" # message not used if it succeeds

Viel Raum für Designverbesserungen.

Die Nachteile sind die Tatsache, dass false nicht hübsch ist (also der Zucker), und andere Dinge, die die Falle auslösen, könnten ein bisschen dumm aussehen. Trotzdem mag ich diese Methode.

3
Paul Hodges

Ich finde es oft nützlich, eine Funktion zu schreiben, um Fehlermeldungen zu behandeln, so dass der Code insgesamt sauberer ist.

# Usage: die [exit_code] [error message]
die() {
  local code=$? now=$(date +%T.%N)
  if [ "$1" -ge 0 ] 2>/dev/null; then  # assume $1 is an error code if numeric
    code="$1"
    shift
  fi
  echo "$0: ERROR at ${now%???}${1:+: $*}" >&2
  exit $code
}

Dadurch wird der Fehlercode aus dem vorherigen Befehl übernommen und beim Beenden des gesamten Skripts als Standardfehlercode verwendet. Es wird auch die Zeit angegeben, wobei Mikrosekunden unterstützt werden (der %N von GNU datei ist Nanosekunden, die wir später auf Mikrosekunden verkürzen).

Wenn die erste Option null oder eine positive ganze Zahl ist, wird sie zum Beendigungscode und wir entfernen ihn aus der Liste der Optionen. Wir melden die Nachricht dann als Standardfehler mit dem Namen des Skripts, dem Wort "ERROR" und der Uhrzeit (wir verwenden die Parametererweiterung, um Nanosekunden auf Mikrosekunden oder für Nicht-GNU-Zeiten zu verkürzen, z. B. 12:34:56.%N auf 12:34:56). . Nach dem Word-Fehler werden ein Doppelpunkt und ein Leerzeichen hinzugefügt, jedoch nur, wenn eine Fehlernachricht vorliegt. Zum Schluss beenden wir das Skript mit dem zuvor bestimmten Beendigungscode, um wie üblich Traps auszulösen.

Einige Beispiele (vorausgesetzt der Code lebt in script.sh):

if [ condition ]; then die 123 "condition not met"; fi
# exit code 123, message "script.sh: ERROR at 14:58:01.234564: condition not met"

$command |grep -q condition || die 1 "'$command' lacked 'condition'"
# exit code 1, "script.sh: ERROR at 14:58:55.825626: 'foo' lacked 'condition'"

$command || die
# exit code comes from command's, message "script.sh: ERROR at 14:59:15.575089"
2
Adam Katz

Sie haben zwei Möglichkeiten: Die Ausgabe des Skripts in eine Datei umleiten, eine Protokolldatei in das Skript einführen und 

  1. Ausgabe in eine Datei umleiten :

Hier gehen Sie davon aus, dass das Skript alle notwendigen Informationen einschließlich Warn- und Fehlermeldungen ausgibt. Sie können die Ausgabe dann in eine Datei Ihrer Wahl umleiten.

./runTests &> output.log

Der obige Befehl leitet sowohl die Standardausgabe als auch die Fehlerausgabe in Ihre Protokolldatei um.

Bei diesem Ansatz müssen Sie keine Protokolldatei in das Skript einfügen. Daher ist die Logik um einiges einfacher.

  1. Eine Protokolldatei in das Skript einfügen :

Fügen Sie in Ihrem Skript eine Protokolldatei hinzu, indem Sie sie entweder hart codieren:

logFile='./path/to/log/file.log'

oder durch einen Parameter übergeben:

logFile="${1}"  # This assumes the first parameter to the script is the log file

Es ist eine gute Idee, den Zeitstempel zum Zeitpunkt der Ausführung der Protokolldatei oben im Skript hinzuzufügen:

date '+%Y%-m%d-%H%M%S' >> "${logFile}"

Sie können dann Ihre Fehlernachrichten in die Protokolldatei umleiten

if [ condition ]; then
    echo "Test cases failed!!" >> "${logFile}"; 
fi

Dadurch wird der Fehler an die Protokolldatei angehängt und die Ausführung fortgesetzt. Wenn Sie die Ausführung anhalten möchten, wenn kritische Fehler auftreten, können Sie das Skript exit verwenden:

if [ condition ]; then
    echo "Test cases failed!!" >> "${logFile}"; 
    # Clean up if needed
    exit 1;
fi

Beachten Sie, dass exit 1 anzeigt, dass das Programm die Ausführung aufgrund eines nicht angegebenen Fehlers stoppt. Sie können dies anpassen, wenn Sie möchten.

Mit diesem Ansatz können Sie Ihre Protokolle anpassen und für jede Komponente Ihres Skripts eine andere Protokolldatei erstellen.


Wenn Sie ein relativ kleines Skript haben oder das Skript eines anderen Benutzers ausführen möchten, ohne es zu ändern, ist der erste Ansatz besser geeignet.

Wenn Sie möchten, dass sich die Protokolldatei immer am selben Ort befindet, ist dies die bessere Option für 2. Auch wenn Sie ein großes Skript mit mehreren Komponenten erstellt haben, möchten Sie möglicherweise jeden Teil anders protokollieren, und der zweite Ansatz ist Ihre einzige Möglichkeit. 

0
alamoot