¿Hay alguna manera de tener una función en mi script bash que se ejecuta automáticamente en cualquier error de comando?

12

Estoy escribiendo un script de shell que necesita hacer un montón de comandos y cada comando depende de cada comando anterior. Si algún comando falla, el script completo debería fallar y llamo a una función de salida. Podría verificar el código de salida de cada comando, pero me pregunto si hay un modo que pueda habilitar o una forma de hacer que bash lo haga automáticamente.

Por ejemplo, tome estos comandos:

cd foo || myfunc
rm a || myfunc
cd bar || myfunc
rm b || myfunc


¿Hay alguna manera en la que pueda indicarle al shell antes de ejecutar estos comandos que debería llamar a myfunc si alguno de ellos falla, para que pueda escribir algo más limpio como:

cd foo
rm a
cd bar
rm b
prueba
fuente

Respuestas:

13

Puede usar bash trap ERR para hacer que su script se cierre si algún comando devuelve un estado mayor que cero y ejecuta su función al salir.

Algo como:

myfunc() {
  echo 'Error raised. Exiting!'
}

trap 'myfunc' ERR

# file does not exist, causing error
ls asd
echo 123

Tenga en cuenta que bash trap ERR está implícito set -o errexito set -eno es POSIX.

Y la ERRtrampa no se ejecuta si el comando no es parte de la lista de comandos inmediatamente después untilo whilepalabra clave, parte de la prueba siguiendo las ifo elifpalabras reservadas, parte de un comando ejecutado en &&o ||lista, o si el estado de retorno del comando está siendo invertida utilizando !.

Cuonglm
fuente
1

Una variación (quizás) más simple en la respuesta aceptada:

  1. Utilícelo set -e para que la falla de un comando anule la ejecución de una lista.
  2. Simplemente enumere sus comandos.
  3. Use una declaración if- then- elsepara ejecutar sus comandos de manejo de errores. Esta última pieza es un poco complicada. Reloj:
conjunto -e
Si
    cmd 1                         # ej., cd foo
     cmd 2                         # ej., rm a
     cmd 3                         # ej., cd bar
     cmd 4                         # ej., rm b
entonces
    set + e
    comandos que hacer en caso de éxito (si corresponde)
más
    set + e
    myfunc
    otros comandos para hacer en caso de falla (si hay) 
fi

La parte difícil es que pones tus comandos en la ifparte de la if- then- else, no en la thenparte o la elseparte. Recuerde que la sintaxis de la ifdeclaración es

si la  lista ; luego  lista ; [  lista elif ; luego  lista ; ] ... [otra  lista ; ] fi 
   ↑↑↑↑
El set -ele dice al shell que, si ( ) falla, no debería continuar y ejecutar ( ), y así sucesivamente. Si esto le sucediera a un comando en el nivel más externo de un script de shell, el shell se cerraría. Sin embargo, dado que · · · es una lista (compuesta) que sigue a una , el fallo de cualquiera de esos cuatro comandos simplemente hace que falle toda la lista, lo que hace que se ejecute la cláusula. Si los cuatro comandos tienen éxito, se ejecuta la cláusula.cmd1cd foocmd2rm acmd1cmd2cmd3cmd4ifelsethen

En cualquier caso, lo primero que debe hacer es probablemente desactivar (desactivar) la eopción haciendo set +e. De lo contrario, la secuencia de comandos podría desaparecer si myfuncfalla un comando .

set -ese especifica y se describe en la especificación POSIX .

G-Man dice 'restablecer a Mónica'
fuente
0

Tome su palabra " cada comando depende de cada comando anterior. Si algún comando falla, el script completo debería fallar " literalmente, creo que no necesita ninguna función especial para tratar los errores.

Todo lo que necesita es encadenar sus comandos con &&operador y ||operador, que hace exactamente lo que escribió.

Por ejemplo, esta cadena se romperá e imprimirá "algo salió mal" si alguno de los comandos anteriores se rompió (bash lee de izquierda a derecha)

cd foo && rm a && cd bar && rm b || echo "something went wrong"

Ejemplo real (creé dir foo, archivo a, barra de directorios y archivo b solo para una demostración real):

gv@debian:/home/gv/Desktop/PythonTests$ cd foo && rm a && cd bar && rm bb || echo "something is wrong"
rm: cannot remove 'bb': No such file or directory
something is wrong #mind the error in the last command

gv@debian:/home/gv/Desktop/PythonTests$ cd foo && rm aa && cd bar && rm b || echo "something is wrong"
rm: cannot remove 'aa': No such file or directory
something is wrong #mind the error in second command in the row

Y finalmente, si todos los comandos se han ejecutado con éxito (código de salida 0), el script simplemente continúa:

gv@debian:/home/gv/Desktop/PythonTests$ cd foo && rm a && cd bar && rm b || echo "something is wrong"
gv@debian:/home/gv/Desktop/PythonTests/foo/bar$ 
# mind that the error message is not printed since all commands were successful.

Es importante recordar que con el uso de && se ejecuta el siguiente comando si el comando anterior salió con el código 0, que para bash significa éxito.

Si algún comando sale mal en la cadena, entonces el comando / script / lo que sigue || será ejecutado.

Y solo para el registro, si necesita realizar diferentes acciones dependiendo del comando que se rompió, también podría hacerlo con un script clásico monitoreando el valor del $?cual informa el código de salida del comando exactamente anterior (devuelve cero si el comando se ejecutó con éxito u otro número positivo si el comando falló)

Ejemplo:

for comm in {"cd foo","rm a","cd bbar","rm b"};do  #mind the error in third command
eval $comm
    if [[ $? -ne 0 ]];then 
        echo "something is wrong in command $comm"
        break
    else 
    echo "command $comm executed succesful"
    fi
done

Salida:

command cd foo executed succesfull
command rm a executed succesfull
bash: cd: bbar: No such file or directory
something is wrong in command cd bbar

Consejo: Puede suprimir el mensaje "bash: cd: bbar: No such file ..." aplicando eval $comm 2>/dev/null

George Vasiliou
fuente