¿Existe un comando TRY CATCH en Bash?
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.
¿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/catch
en bash; sin embargo, se puede lograr un comportamiento similar usando &&
o ||
.
Usando ||
:
si command1
falla, command2
se ejecuta de la siguiente manera
command1 || command2
&&
De manera similar , el uso command2
se ejecutará si command1
tiene éxito.
La aproximación más cercana try/catch
es 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.
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
}
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 .
bash
no aborta la ejecución en caso de que algo detecte un estado de error (a menos que establezca la -e
bandera). Los lenguajes de programación que ofrecen try/catch
hacen 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_condition
con un, if
tambié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 -e
indicador mencionado anteriormente en el shell, incluso puedes eliminar esa exit
declaració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"
)
...