¿Hay un comando TRY CATCH en Bash


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

Author: codeforester, 2014-02-25

10 answers

¿Hay un comando TRY CATCH en Bash?

No.

Bash no tiene tantos lujos como uno puede encontrar en muchos lenguajes de programación.

No hay try/catch en bash; sin embargo, uno puede lograr un comportamiento similar usando && o ||.

Usando ||:

Si command1 falla entonces command2 se ejecuta de la siguiente manera

command1 || command2

Del mismo modo, usando &&, command2 se ejecutará si command1 tiene éxito

La aproximación más cercana de try/catch es como sigue

{ # try

    command1 &&
    #save your output

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

También bash contiene algunos mecanismos de manejo de errores, así como

set -e

Detendrá inmediatamente su script si un comando simple falla. Creo que este debería haber sido el comportamiento predeterminado. Dado que tales errores casi siempre significan algo inesperado, no es realmente 'sano' seguir ejecutando los siguientes comandos.

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

 322
Author: Jayesh Bhoi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-08-05 01:53:39

Basado en algunas respuestas que encontré aquí, me hice un pequeño archivo de ayuda para obtener la fuente de 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

    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
}
 70
Author: Mathias Henze,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-08-07 10:35:56

He desarrollado una implementación casi impecable de try & catch 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 ellos 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 bash boilerplate/framework. Amplía aún más la idea de try & catch con cosas como el manejo de errores con backtrace y excepciones (además de algunas otras características agradables).

Aquí está el código que es responsable solo de intentar & captura:

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.

 51
Author: niieani,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-05-03 22:14:23

bash no aborta la ejecución en ejecución en caso de que sth detecte un estado de error (a menos que establezca el indicador -e). Los lenguajes de programación que ofrecen try/catch hacen esto para inhibir un "rescate" debido a esta situación especial (por lo tanto típicamente llamada "excepción").

En el bash, en cambio, solo el comando en cuestión saldrá con un código de salida mayor que 0, indicando ese estado de error. Usted puede comprobar para que por supuesto, pero ya que no hay automático rescatandode cualquier cosa, un try/catch no tiene sentido. Simplemente le falta ese contexto.

Puede, sin embargo, simular un rescate mediante el uso de sub shells que pueden terminar en un punto que usted decida:

(
  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_condition con un if también puedes probar un comando, y en caso de que falle (tenga un código de salida mayor que 0), bail out:

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

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

Si necesita más información para pasar junto con su excepción simulada, puede usar el stdout de las subcapas, pero eso es un poco complicado y tal vez otra pregunta; -)

Usando la bandera antes mencionada -e al shell puedes incluso quitar esa declaración explícita exit:

(
  set -e
  echo "Do one thing"
  echo "Do another thing"
  some_command
  echo "Do yet another thing"
  echo "And do a last thing"
)
...
 11
Author: Alfe,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-02-25 12:15:31

Como todo el mundo dice, bash no tiene una sintaxis try/catch soportada por el lenguaje adecuado. Puede lanzar bash con el argumento -e o usar set -e dentro del script para abortar todo el proceso bash si cualquier comando tiene un código de salida distinto de cero. (También puede set +e para permitir temporalmente comandos fallidos.)

Entonces, una técnica para simular un bloque try/catch es lanzar un subproceso para hacer el trabajo con -e habilitado. Luego, en el proceso principal, verifique el código de devolución del subproceso.

Bash soporta cadenas heredoc, por lo que no tiene que escribir dos archivos separados para manejar esto. En el siguiente ejemplo, TRY heredoc se ejecutará en una instancia de bash separada, con -e habilitado, por lo que el subproceso se bloqueará si cualquier comando devuelve un código de salida distinto de cero. Luego, de vuelta en el proceso principal, podemos verificar el código de retorno para manejar un bloque catch.

#!/bin/bash

set +e
bash -e <<TRY
  echo hello
  cd /does/not/exist
  echo world
TRY
if [ $? -ne 0 ]; then
  echo caught exception
fi

No es un bloque try/catch soportado por el lenguaje adecuado, pero puede rascar un picor similar para usted.

 10
Author: Dan Fabulich,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-08-28 17:43:31

Puedes usar trap:

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

Se traduce como:

(
  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
)
 6
Author: Mark K Cowan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-06-23 23:43:53

Hay muchas soluciones similares que probablemente funcionan. A continuación se muestra una manera simple y de trabajo para lograr try/catch, con explicación en los comentarios.

#!/bin/bash

function a() {
  # do some stuff here
}
function b() {
  # do more stuff here
}

# this subshell is a scope of try
# try
(
  # this flag will make to exit from current subshell on any error
  # inside it (all functions run inside will also break on any error)
  set -e
  a
  b
  # do more stuff here
)
# and here we catch errors
# catch
errorCode=$?
if [ $errorCode -ne 0 ]; then
  echo "We have an error"
  # We exit the all script with the same error, if you don't want to
  # exit it and continue, just delete this line.
  exit $errorCode
fi
 6
Author: Kostanos,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-10-06 03:12:28

Y tienes trampas http://www.tldp.org/LDP/Bash-Beginners-Guide/html/sect_12_02.html que no es lo mismo, pero otra técnica que se puede utilizar para este propósito

 4
Author: Eran Ben-Natan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-02-25 12:58:54

Puedes hacer:

#!/bin/bash
if <command> ; then # TRY
    <do-whatever-you-want>
else # CATCH
    echo 'Exception'
    <do-whatever-you-want>
fi
 1
Author: Davidson Lima,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-01-02 19:42:26

Una cosa muy simple que uso:

try() {
    "$@" || (e=$?; echo "$@" > /dev/stderr; exit $e)
}
 0
Author: syberghost,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-10-06 13:25:09