631 Stimmen

Gibt es einen TRY CATCH Befehl in Bash?

Ich schreibe ein Shell-Skript und muss überprüfen, ob eine Terminal-App installiert ist. Ich möchte einen TRY/CATCH-Befehl verwenden, um dies zu tun, es sei denn, es gibt einen eleganteren Weg.

943voto

Jayesh Bhoi Punkte 22636

Gibt es einen TRY CATCH Befehl in Bash?

Nein.

Bash hat nicht so viele Annehmlichkeiten wie man sie in vielen Programmiersprachen finden kann.

Es gibt kein try/catch in bash; man kann jedoch ein ähnliches Verhalten mit && oder || erreichen.

Verwendung von ||:

Wenn Befehl1 fehlschlägt, wird Befehl2 wie folgt ausgeführt

Befehl1 || Befehl2

Ebenso wird mit && Befehl2 ausgeführt, wenn Befehl1 erfolgreich ist

Die nächste Annäherung an try/catch ist wie folgt

{ # try

    Befehl1 &&
    # speichern Sie Ihre Ausgabe

} || { # catch
    # speichern Sie das Protokoll für die Ausnahme
}

Auch bash enthält einige Fehlerbehandlungsmechanismen

set -e

es stoppt Ihr Skript, wenn ein einfacher Befehl fehlschlägt.

Und warum nicht if...else. Es ist Ihr bester Freund.

188voto

Mathias Henze Punkte 2230

Basierend auf einigen Antworten, die ich hier gefunden habe, habe ich mir eine kleine Hilfsdatei erstellt, die ich für meine Projekte verwenden kann:

trycatch.sh

#!/bin/bash

function try()
{
    [[ $- = *e* ]]; SAVED_OPT_E=$?
    set +e
}

function throw()
{
    exit $1
}

function catch()
{
    export ex_code=$?
    (( $SAVED_OPT_E )) && set +e
    return $ex_code
}

function throwErrors()
{
    set -e
}

function ignoreErrors()
{
    set +e
}

Hier ist ein Beispiel, wie es im Einsatz aussieht:

#!/bin/bash
export AnException=100
export AnotherException=101

# start with a try
try
(   # open a subshell !!!
    echo "tue etwas"
    [ someErrorCondition ] && throw $AnException

    echo "tue noch etwas"
    executeCommandThatMightFail || throw $AnotherException

    throwErrors # Beende automatisch den try-Block, wenn das Befehls-ergebnis nicht null ist
    echo "jetzt zu etwas ganz anderem"
    executeCommandThatMightFail

    echo "es ist ein Wunder, dass wir so weit gekommen sind"
    executeCommandThatFailsForSure || true # Ignoriere einen einzigen fehlschlagenden Befehl

    ignoreErrors # Ignoriere Fehler von Befehlen bis auf weiteres
    executeCommand1ThatFailsForSure
    local result = $(executeCommand2ThatFailsForSure)
    [ result != "erwarteter Fehler" ] && throw $AnException # ok, wenn es kein erwarteter Fehler ist, wollen wir abbrechen!
    executeCommand3ThatFailsForSure

    # Stellen Sie sicher, dass $ex_code gelöscht wird, ansonsten wird 'catch *' ausgeführt
    # echo "fertig" erledigt den Trick für dieses Beispiel
    echo "fertig"
)
# Direkt nachdem Sie die Subshell geschlossen haben, müssen Sie eine Verbindung zu 'catch' herstellen, 
# indem Sie || und dann ein Gruppenzeichen anhängen
catch || {
    # Jetzt können Sie behandeln
    case $ex_code in
        $AnException)
            echo "AnException wurde ausgelöst"
        ;;
        $AnotherException)
            echo "AnotherException wurde ausgelöst"
        ;;
        *)
            echo "Es wurde eine unerwartete Ausnahme ausgelöst"
            throw $ex_code # Sie können die "Ausnahme" erneut werfen, was bewirkt, dass das Skript abbricht, wenn sie nicht abgefangen wird
        ;;
    esac
}

84voto

niieani Punkte 3693

Ich habe eine nahezu fehlerlose Try & Catch-Implementierung in Bash entwickelt, die es Ihnen ermöglicht, Code wie folgt zu schreiben:

try 
    echo 'Hallo'
    false
    echo 'Dies wird nicht angezeigt'

catch 
    echo "Fehler in $__EXCEPTION_SOURCE__ in Zeile: $__EXCEPTION_LINE__!"

Sie können sogar die Try-Catch-Blöcke ineinander verschachteln!

try {
    echo 'Hallo'

    try {
        echo 'Verschachteltes Hallo'
        false
        echo 'Dies wird nicht ausgeführt'
    } catch {
        echo "Verschachtelt erfasst (@ $__EXCEPTION_LINE__)"
    }

    false
    echo 'Dies wird auch nicht ausgeführt'

} catch {
    echo "Fehler in $__EXCEPTION_SOURCE__ in Zeile: $__EXCEPTION_LINE__!"
}

Der Code ist Teil meines Bash-Boilerplate/Frameworks. Es erweitert die Idee von Try & Catch mit Dingen wie Fehlerbehandlung mit Stapelverfolgung und Ausnahmen (plus einigen anderen nützlichen Funktionen).

Hier ist der Code, der nur für Try & Catch verantwortlich ist:

set -o pipefail
shopt -s expand_aliases
declare -ig __oo__insideTryCatch=0

# Wenn Try-Catch verschachtelt ist, dann setze +e davor, damit der übergeordnete Handler uns nicht erfasst
alias try="[[ \$__oo__insideTryCatch -gt 0 ]] && set +e;
           __oo__insideTryCatch+=1; ( set -e;
           trap \"Exception.Capture \${LINENO}; \" ERR;"
alias catch=" ); Exception.Extract \$? || "

Exception.Capture() {
    local script="${BASH_SOURCE[1]#./}"

    if [[ ! -f /tmp/stored_exception_source ]]; then
        echo "$script" > /tmp/stored_exception_source
    fi
    if [[ ! -f /tmp/stored_exception_line ]]; then
        echo "$1" > /tmp/stored_exception_line
    fi
    return 0
}

Exception.Extract() {
    if [[ $__oo__insideTryCatch -gt 1 ]]
    then
        set -e
    fi

    __oo__insideTryCatch+=-1

    __EXCEPTION_CATCH__=( $(Exception.GetLastException) )

    local retVal=$1
    if [[ $retVal -gt 0 ]]
    then
        # RÜCKWÄRTSKOMPATIBLE WEISE:
        # export __EXCEPTION_SOURCE__="${__EXCEPTION_CATCH__[(${#__EXCEPTION_CATCH__[@]}-1)]}"
        # export __EXCEPTION_LINE__="${__EXCEPTION_CATCH__[(${#__EXCEPTION_CATCH__[@]}-2)]}"
        export __EXCEPTION_SOURCE__="${__EXCEPTION_CATCH__[-1]}"
        export __EXCEPTION_LINE__="${__EXCEPTION_CATCH__[-2]}"
        export __EXCEPTION__="${__EXCEPTION_CATCH__[@]:0:(${#__EXCEPTION_CATCH__[@]} - 2)}"
        return 1 # damit wir mit einem "catch" fortfahren können
    fi
}

Exception.GetLastException() {
    if [[ -f /tmp/stored_exception ]] && [[ -f /tmp/stored_exception_line ]] && [[ -f /tmp/stored_exception_source ]]
    then
        cat /tmp/stored_exception
        cat /tmp/stored_exception_line
        cat /tmp/stored_exception_source
    else
        echo -e " \n${BASH_LINENO[1]}\n${BASH_SOURCE[2]#./}"
    fi

    rm -f /tmp/stored_exception /tmp/stored_exception_line /tmp/stored_exception_source
    return 0
}

Fühlen Sie sich frei, es zu verwenden, zu duplizieren und beizutragen - es ist auf GitHub.

39voto

Alfe Punkte 51658

bash beendet die laufende Ausführung nicht, wenn ein Fehlerzustand erkannt wird (es sei denn, Sie setzen das Flag -e). Programmiersprachen, die try/catch anbieten, tun dies, um ein "Abbruch" aufgrund dieser besonderen Situation zu verhindern (typischerweise "Ausnahme" genannt).

In bash hingegen wird nur der betreffende Befehl mit einem Exit-Code größer als 0 beendet, um den Fehlerzustand anzuzeigen. Natürlich können Sie das überprüfen, aber da es kein automatisches Abbrechen von allem gibt, ergibt ein try/catch keinen Sinn. Es fehlt einfach dieser Kontext.

Sie können jedoch ein Abbrechen simulieren, indem Sie Sub-Shell verwenden, die an einem von Ihnen festgelegten Punkt beendet werden kann:

(
  echo "Etwas tun"
  echo "Etwas anderes tun"
  if some_condition
  then
    exit 3  # <-- dies ist unser simuliertes Abbruch
  fi
  echo "Noch etwas anderes tun"
  echo "Und zuletzt etwas tun"
)   # <-- hier kommen wir nach dem simulierten Abbruch an, und $? wird 3 sein (Exit-Code)
if [ $? = 3 ]
then
  echo "Abbruch erkannt"
fi

Anstelle von some_condition mit einem if können Sie auch einfach einen Befehl versuchen und im Falle eines Fehlers (Exit-Code größer als 0) abbrechen:

(
  echo "Etwas tun"
  echo "Etwas anderes tun"
  some_command || exit 3
  echo "Noch etwas anderes tun"
  echo "Und zuletzt etwas tun"
)
...

Leider sind Sie bei Verwendung dieser Technik auf 255 verschiedene Exit-Codes (1..255) beschränkt, und es können keine vernünftigen Ausnahmeobjekte verwendet werden.

Wenn Sie weitere Informationen benötigen, die mit Ihrer simulierten Ausnahme weitergegeben werden sollen, können Sie den Standardausgang der Subshells verwenden, aber das ist etwas kompliziert und vielleicht eine andere Frage ;-)

Mit dem oben genannten Flag -e für die Shell können Sie sogar diese explizite exit-Anweisung entfallen lassen:

(
  set -e
  echo "Etwas tun"
  echo "Etwas anderes tun"
  some_command
  echo "Noch etwas anderes tun"
  echo "Und zuletzt etwas tun"
)
...

36voto

Mark K Cowan Punkte 1657

Sie können trap verwenden:

try { block A } catch { block B } finally { block C }

übersetzt zu:

(
  set -Ee
  function _catch {
    block B
    exit 0  # optional; use if you don't want to propagate (rethrow) error to outer shell
  }
  function _finally {
    block C
  }
  trap _catch ERR
  trap _finally EXIT
  block A
)

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X