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)
}
Y tiene trampas http://www.tldp.org/LDP/Bash-Beginners-Guide/html/sect_12_02.html que no es la misma, pero hay otra técnica que puede usar para este propósito
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"
)
...