¿Existe un comando TRY CATCH en Bash?

Resuelto Lee Probert asked hace 10 años • 18 respuestas

Estoy escribiendo un script de shell y necesito verificar que se haya instalado una aplicación de terminal. Quiero usar un comando TRY/CATCH para hacer esto a menos que haya una manera más clara.

Lee Probert avatar Feb 25 '14 16:02 Lee Probert
Aceptado

¿Existe un comando TRY CATCH en Bash?

No.

Bash no tiene tantos lujos como los que se pueden encontrar en muchos lenguajes de programación.

No hay nada try/catchen bash; sin embargo, se puede lograr un comportamiento similar usando &&o ||.

Usando ||:

si command1falla, command2se ejecuta de la siguiente manera

command1 || command2

&&De manera similar , el uso command2se ejecutará si command1tiene éxito.

La aproximación más cercana try/catches la siguiente

{ # try

    command1 &&
    #save your output

} || { # catch
    # save log for exception 
}

Además, bash contiene algunos mecanismos de manejo de errores.

set -e

detiene su script si falla algún comando simple.

Y también por qué no if...else. Es tu mejor amigo.

Jayesh Bhoi avatar Feb 25 '2014 09:02 Jayesh Bhoi

Basado en algunas respuestas que encontré aquí, creé un pequeño archivo de ayuda para mis proyectos:

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
}

Aquí hay un ejemplo de cómo se ve en uso:

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

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

    echo "do something more"
    executeCommandThatMightFail || throw $AnotherException

    throwErrors # automaticatly end the try block, if command-result is non-null
    echo "now on to something completely different"
    executeCommandThatMightFail

    echo "it's a wonder we came so far"
    executeCommandThatFailsForSure || true # ignore a single failing command

    ignoreErrors # ignore failures of commands until further notice
    executeCommand1ThatFailsForSure
    local result = $(executeCommand2ThatFailsForSure)
    [ result != "expected error" ] && throw $AnException # ok, if it's not an expected error, we want to bail out!
    executeCommand3ThatFailsForSure

    # make sure to clear $ex_code, otherwise catch * will run
    # echo "finished" does the trick for this example
    echo "finished"
)
# directly after closing the subshell you need to connect a group to the catch using ||
catch || {
    # now you can handle
    case $ex_code in
        $AnException)
            echo "AnException was thrown"
        ;;
        $AnotherException)
            echo "AnotherException was thrown"
        ;;
        *)
            echo "An unexpected exception was thrown"
            throw $ex_code # you can rethrow the "exception" causing the script to exit if not caught
        ;;
    esac
}
Mathias Henze avatar Aug 07 '2014 10:08 Mathias Henze

He desarrollado una implementación try & catch casi perfecta en bash, que te permite escribir código como:

try 
    echo 'Hello'
    false
    echo 'This will not be displayed'

catch 
    echo "Error in $__EXCEPTION_SOURCE__ at line: $__EXCEPTION_LINE__!"

¡Incluso puedes anidar los bloques try-catch dentro de sí mismos!

try {
    echo 'Hello'

    try {
        echo 'Nested Hello'
        false
        echo 'This will not execute'
    } catch {
        echo "Nested Caught (@ $__EXCEPTION_LINE__)"
    }

    false
    echo 'This will not execute too'

} catch {
    echo "Error in $__EXCEPTION_SOURCE__ at line: $__EXCEPTION_LINE__!"
}

El código es parte de mi marco/repetitivo bash . Amplía aún más la idea de probar y detectar con cosas como el manejo de errores con seguimiento y excepciones (además de algunas otras características interesantes).

Aquí está el código responsable sólo de intentar y capturar:

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

# if try-catch is nested, then set +e before so the parent handler doesn't catch us
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
        # BACKWARDS COMPATIBILE WAY:
        # 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 # so that we may continue with a "catch"
    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
}

Siéntete libre de usar, bifurcar y contribuir: está en GitHub .

niieani avatar Apr 30 '2015 19:04 niieani

bashno aborta la ejecución en caso de que algo detecte un estado de error (a menos que establezca la -ebandera). Los lenguajes de programación que ofrecen try/catchhacen esto para inhibir un "rescate" debido a esta situación especial (de ahí que normalmente se llame "excepción").

En cambio, en el bash, solo el comando en cuestión saldrá con un código de salida mayor que 0, lo que indica ese estado de error. Puedes comprobarlo, por supuesto, pero como no hay ningún rescate automático de nada, un intento/captura no tiene sentido. Simplemente le falta ese contexto.

Sin embargo, puedes simular un rescate utilizando subcapas que pueden terminar en el punto que tú decidas:

(
  echo "Do one thing"
  echo "Do another thing"
  if some_condition
  then
    exit 3  # <-- this is our simulated bailing out
  fi
  echo "Do yet another thing"
  echo "And do a last thing"
)   # <-- here we arrive after the simulated bailing out, and $? will be 3 (exit code)
if [ $? = 3 ]
then
  echo "Bail out detected"
fi

En lugar de eso some_conditioncon un, iftambién puedes probar un comando y, en caso de que falle (tiene un código de salida mayor que 0), rescatarlo:

(
  echo "Do one thing"
  echo "Do another thing"
  some_command || exit 3
  echo "Do yet another thing"
  echo "And do a last thing"
)
...

Desafortunadamente, al utilizar esta técnica, está restringido a 255 códigos de salida diferentes (1..255) y no se pueden utilizar objetos de excepción decentes.

Si necesita más información para transmitir junto con su excepción simulada, puede usar la salida estándar de los subshells, pero eso es un poco complicado y tal vez sea otra pregunta ;-)

Usando el -eindicador mencionado anteriormente en el shell, incluso puedes eliminar esa exitdeclaración explícita:

(
  set -e
  echo "Do one thing"
  echo "Do another thing"
  some_command
  echo "Do yet another thing"
  echo "And do a last thing"
)
...
Alfe avatar Feb 25 '2014 12:02 Alfe