try handling error catch bash shell error-handling

catch - bash error handling



¿Hay un comando TRY CATCH en Bash? (10)

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 forma más limpia.


¿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, se puede lograr un comportamiento similar utilizando && o || .

Utilizando || :

si command1 falla, command2 ejecuta de la siguiente manera

command1 || command2

De manera similar, al usar && , command2 se ejecutará si command1 es exitoso

La aproximación más cercana de try/catch es la siguiente

{ # try command1 && #save your output } || { # catch # save log for exception }

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

set -e

Inmediatamente detendrá su script si falla un simple comando. Creo que este debería haber sido el comportamiento por defecto. Dado que tales errores casi siempre significan algo inesperado, no es realmente "sensato" seguir ejecutando los siguientes comandos.

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


Basándome en algunas respuestas que encontré aquí, me hice un pequeño archivo de ayuda para 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 }


Como dice todo el mundo, bash no tiene una sintaxis adecuada de prueba / captura compatible con el idioma. Puede iniciar bash con el argumento -e o usar set -e dentro del script para abortar todo el proceso de bash si algún comando tiene un código de salida distinto de cero. (También puede set +e para permitir temporalmente los 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 retorno del subproceso.

Bash admite cadenas heredoc, por lo que no tiene que escribir dos archivos separados para manejar esto. En el siguiente ejemplo, el TRY heredoc se ejecutará en una instancia de bash separada, con -e habilitado, por lo que el subproceso se bloqueará si algún 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 de prueba / captura compatible con el lenguaje adecuado, pero puede causarle una picazón similar.


Hay tantas soluciones similares que probablemente funcionan. A continuación se muestra una forma sencilla y práctica de realizar la prueba / captura, con una 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


He desarrollado una implementación casi perfecta de prueba y captura 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 plantilla / marco de bash . Además, amplía la idea de probar y capturar cosas como el manejo de errores con retroceso y excepciones (más algunas otras características interesantes).

Aquí está el código que es solo responsable de probar y atrapar:

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éntase libre de usar, bifurcar y contribuir, está en GitHub .


Puedes usar trap :

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

se traduce a

( 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 )


Tu puedes hacer:

#!/bin/bash if <command> ; then # TRY <do-whatever-you-want> else # CATCH echo ''Exception'' <do-whatever-you-want> fi


Una cosa muy simple que uso:

try() { "$@" || (e=$?; echo "$@" > /dev/stderr; exit $e) }



bash no aborta la ejecución de la ejecución en caso de que sth detecte un estado de error (a menos que establezca la -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, lo que indica el estado de error. Por supuesto, puede verificarlo, pero como no hay un rescate automático de nada, un intento / captura no tiene sentido. Solo falta ese contexto.

Sin embargo, puede 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 que some_condition with a if también puede probar un comando, y en caso de que falle (tenga un código de salida mayor que 0), descargue:

( 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 usar esta técnica, está restringido a 255 códigos de salida diferentes (1..255) y no se pueden usar 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 subshells, pero eso es un poco complicado y quizás otra pregunta ;-)

Usando la -e mencionada anteriormente en el shell, incluso puede eliminar esa declaración de exit 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" ) ...