Traps y señales en bash
Recupero un viejo post de una antigua web acerca de cómo usar traps y señales en bash.
Algunos problemas típicos a la hora de crear scripts en bash son estos:
- Crear ficheros de lock para, por ejemplo, evitar la ejecución de más de una instancia del script
- Hacer limpieza de ficheros temporales si el script acaba inesperadamente
Estas cuestiones y muchas más se pueden resolver gracias al built-in trap
. Este comando permite capturar ciertas señales y definir la acción que se realiza cuando son recibidas por el script.
Obviamente, hay ciertas señales que no se pueden redefinir como son:
9) SIGKILL
18) SIGCONT
19) SIGSTOP
La lista completa se puede ver con el comando:
$ kill -l
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX
Algunas señales particularmente interesantes para los casos arriba indicados son SIGHUP
, SIGINT
, SIGQUIT
y SIGTERM
:
Señal | Descripción |
---|---|
SIGHUP | Históricamente era la señal que indicaba que el terminal al otro lado de la línea serie había “colgado”. Actualmente indica que el terminal controller se ha cerrado y suele redefinirse para recargar configuración y reabrir ficheros de log |
SIGINT | Interrupción, típicamente Ctrl+C en el teclado |
SIGQUIT | Enviado por el controlling terminal para terminar un programa (¿y generar un core dump? |
SIGTERM | Similar a SIGINT, indica la terminación de un programa |
Pues bien, estas señales se pueden capturar, redefinir o ignorar. Por ejemplo, podemos redefinir la acción que se ejecuta por defecto al recibir la señal que se genera al pulsar Ctrl+C y decidir que, en lugar de terminar el programa, nos muestre un simpático mensaje:
$ cat ctrlc.sh
#!/bin/bash
trap 'echo "Paso de ti..."' SIGINT
while true; do
sleep 1
done
Si lo ejecutamos y tratamos de interrumpir el programa con Ctrl+C…
$ chmod 755 ctrlc.sh
$ ./ctrlc.sh
^CPaso de ti...
^CPaso de ti...
^CPaso de ti...
Mmmm… ahora, claro, habrá que ingeniárselas para parar el programa de otra forma. Esto quiere decir que hay que ser cuidadoso redefiniendo señales.
El formato del comando trap
es el siguiente:
trap 'lista de comandos' SEÑAL1 [SEÑAL2 ...]
Además, la señal se puede ignorar…
trap '' SIGNAL
o resetear a su valor por defecto (definido en signal.h
):
trap - SIGNAL
Con esto ya podemos ir haciendo cosas; si tenemos un script que genera ficheros temporales que queremos eliminar si termina de forma inesperada, podemos redefinir ciertas señales de este modo:
#!/bin/bash
#
# Borrar fichero temporal:
trap '/bin/rm /tmp/mitemp.$$; exit' SIGHUP SIGINT SIGQUIT SIGTERM
Rizando el rizo, se podría añadir la pseudoseñal ERR
. Si ERR
está en la lista de señales, la lista de comandos indicada definida en el trap se ejecutará si se produce un error (código de salida no-cero) en el script, salvo en estas circunstancias:
- El comando que ha fallado forma parte de una lista que sigue a while o until
- Es parte de un test en una sentencia if
- Es parte de una lista de comandos && o ||
- La salida del comando está invertida via !
Así, por ejemplo:
$ cat err.sh
#!/bin/bash -e
# Defino el trap
trap 'echo "Se ha producido un error"' ERR
touch testfile.txt
chmod 444 testfile.txt
echo "esto va a fallar" >testfile.txt
echo "esto no se va a ejecutar"
Cuando se ejecuta ocurre esto:
$ ./err.sh
./err.sh: line 7: testfile.txt: Permission denied
Se ha producido un error
Nótese que, al ejecutarse bash con la opción -e
, termina tras el error y no llega a ejecutarse la última línea. Esto me recuerda que algún día habrá que escribir sobre los modificadores de bash, esos grandes desconocidos :)
Hay otra pseudoseñal interesante, EXIT
, que se dispara cuando el script termina.
Y pensaba terminar con lo de los ficheros de lock, pero se quedará para una segunda parte.