3.6.4. Captura de eventos¶
En ocasiones nos interesa ejecutar una acción o un conjunto de acciones al término del programa (p.e. borrar un archivo temporal creado durante la ejecución del script). Para ello existe la orden interna trap:
trap [cadena_de_ordenes] SEÑAL1 [SEÑAL2 ...]
La cadena_de_ordenes es una cadena que contiene la orden u órdenes que queremos ejecutar, mientras que a continuación se incluye la lista de señales para las que queremos que se ejecuten las órdenes. Por ejemplo:
TMPFILE=$(mktemp)
trap 'rm -f $TMPFILE' EXIT TERM INT
[... Continua el script ...]
Esto hará que se borre el fichero temporal que hemos creado, cuando el script
acaba (EXIT
), se interrumple con Ctrl+C (INT
) o se interrumpe de forma
amable (con un kill -1
, p.e.). Un listado de señales posibles se puede
obtener (en bash) de:
$ trap -l
La señal se puede incluir en trap usando el número asociado o el nombre
eliminando SIG-
. Además de estas señales, hay otros sucesos que pueden ser
capturados:
EXIT
(también el código 0),que se produce cuando el programa acaba, bien porque no hay más código bien porque se topa con una orden exit[1].
DEBUG
(sólo bash),que se produce justamente antes de que una orden se ejecute.
ERR
(sólo bash),que se produce cada vez que un comando falla.
RETURN
(sólo bash),que se produce cada vez que una función termina su ejecución.
En cuanto a la cadena de órdenes hay dos que tienen significado especial:
La cadena vacía provoca que la señal se ignore. Por ejemplo:
#!/bin/sh echo 'Te vas a chupar diez segundos de espera, quieras o no' trap '' INT TERM sleep 10
Nota
En realidad, esto no es un significado espacial. Literalmente, se hace lo que hemos dicho, nada, aunque hubiera alguna acción predefinida para esa señal.
La ausencia de cadena o el guión («-») hacen que la señal vuelva a su comportamiento habitual, o sea, que se anule un trap que previamente hubieramos hecho:
#!/bin/sh echo 'Te vas a chupar diez segundos de espera, quieras o no' trap '' INT TERM sleep 10 echo 'Pero ahora sí te doy la oportunidad de que te canses de mí antes' trap - INT TERM sleep 10
Advertencia
El efecto no es acomulativo: si redefinimos la acción para una señal, la acción que hubiéramos asociado previamente, ya no se hará. Vea más adelante cómo solvertar esto.
trap también puede usarse sin argumento alguno, en cuyo caso mostrará las acciones definidas para cada señal:
$ trap 'echo "Hola"' INT
$ trap
trap -- 'echo "Hola"' SIGINT
Advertencia
Aunque el estándar POSIX dicta que un trap que se ejecute en una subshell, debe imprimir las acciones definidas en la shell padre:
$ echo $(trap)
trap -- 'echo "Hola"' SIGINT
$ trap | grep '^INT$'
trap -- 'echo "Hola"' SIGINT
dash no lo hace y no devuelve nada[2] (sí cumplen el estándar bash, como ilustra el ejemplo, y busybox).
Por último, ya que trap redefine, puede ser interesante disponer de una función que permita acomular acciones ante una señal o evento:
#
# Permite acomular acciones a señales y eventos.
# Uso:
# trapplus "accion a realizar" SEÑAL1 [SEÑAL2... ]
#
# Las señales deben indicarse con su nombre, no su número.
#
trapplus() {
local ACTION="$1" pre
shift
tmpfile=$(mktemp -u)
for signal in "$@"; do
# Para que funcione con dash (que incumple POSIX), hay que montar este sindios.
{ trap ; pre=$(awk '$NF ~ /^(SIG)?'"$signal"'$/ { $NF=""; print }'); rm -f "$tmpfile"; } > "$tmpfile" < "$tmpfile"
if [ -n "$pre" ]; then
eval "${pre%\' }; $ACTION' $signal"
else
trap -- "$ACTION" "$signal"
fi
done
}
Notas al pie