¿"Trampa ... SALIDA INT TERMINAL" realmente necesaria?

63

Muchos ejemplos para trapusar trap ... INT TERM EXITen tareas de limpieza. Pero, ¿es realmente necesario enumerar las tres sigspecs?

El manual dice:

Si un SIGNAL_SPEC es EXIT (0) ARG se ejecuta al salir del shell.

lo cual creo que se aplica si el script terminó normalmente o si terminó porque recibió SIGINTo SIGTERM. Un experimento también confirma mi creencia:

$ cat ./trap-exit
#!/bin/bash
trap 'echo TRAP' EXIT
sleep 3
$ ./trap-exit & sleep 1; kill -INT %1
[1] 759
TRAP
[1]+  Interrupt               ./trap-exit
$ ./trap-exit & sleep 1; kill -TERM %1
[1] 773
TRAP
[1]+  Terminated              ./trap-exit

Entonces, ¿por qué tantos ejemplos enumeran todo INT TERM EXIT? ¿O me perdí algo y hay algún caso en el EXITque faltaría una suela ?

musiphil
fuente
3
También tenga en cuenta que con una especificación como INT TERM EXITel código de limpieza se ejecuta dos veces cuando SIGTERMo SIGINTse recibe.
maxschlepzig

Respuestas:

19

La especificación POSIX no dice mucho sobre las condiciones que resultan en la ejecución de la trampa EXIT, solo sobre cómo debe verse su entorno cuando se ejecuta.

En el caparazón de cenizas de Busybox, su prueba de salida de trampa no hace eco 'TRAP' antes de salir debido a SIGINT o SIGTERM. Sospecho que existen otros proyectiles que pueden no funcionar de esa manera.

# /tmp/test.sh & sleep 1; kill -INT %1
# 
[1]+  Interrupt                  /tmp/test.sh
# 
# 
# /tmp/test.sh & sleep 1; kill -TERM %1
# 
[1]+  Terminated                 /tmp/test.sh
# 
Shawn J. Goff
fuente
3
dashTampoco se atrapa justo EXITcuando recibe SIGINT/SIGTERM.
maxschlepzig
44
zshtambién, por lo tanto, tal vez bashsea ​​el único shell donde EXITtambién coinciden las señales.
maxschlepzig
@maxschlepzig zshno se atrapa EXITcuando recibe INT, pero lo hace cuando recibe TERM. EDITAR: Acabo de notar cuántos años tenía esto ...
JoL
27

Sí, hay una diferencia.

Este script saldrá cuando lo presione Enter, o lo envíe SIGINTo SIGTERM:

trap '' EXIT
echo ' --- press ENTER to close --- '
read response

Este script saldrá cuando presione Enter:

trap '' EXIT INT TERM
echo ' --- press ENTER to close --- '
read response

* Probado en sh , Bash y Zsh . (ya no funciona en sh cuando agrega un comando para que se ejecute trap)


También está lo que dijo @Shawn: Ash y Dash no atrapan las señales EXIT.

Entonces, para manejar las señales de manera robusta, es mejor evitar las trampas por EXITcompleto y usar algo como esto:

cleanup() {
    echo "Cleaning stuff up..."
    exit
}

trap cleanup INT TERM
echo ' --- press ENTER to close --- '
read var
cleanup
Zaz
fuente
1
La solución con la limpieza hace lo correcto: ¡muy elegante! Se ha convertido en un modismo para mis scripts de bash con mktempllamadas.
Bjoern Dahlgren
2
¿Es eso exitnecesario en cleanup?
jarno
3
Esto no funciona si tiene errores shellscript en su código que hacen que se cierre prematuramente.
ijw
2
@ijw: En Bash y Ksh, puedes atraparlo ERRpara manejar eso, pero no es portátil .
Zaz
55
Esta solución no es robusta cuando otro shell la llama. No maneja la espera en la salida cooperativa ; querrá trap - INT TERM; kill -2 $$como última línea de limpieza, decirle al shell principal que salió prematuramente. Si un shell primario foobar.sh llama a su script (foo.sh), y luego llama a bar.sh, no desea que bar.sh se ejecute si se envía INT / TERM a su foo.sh. trap cleanup EXITmanejará esta propagación automáticamente, por lo que es IMO la más robusta. También significa que no tendría que llamar cleanupal final del guión.
Nicholas Pipitone
12

Refinando la última respuesta, porque tiene problemas:

# Our general exit handler
cleanup() {
    err=$?
    echo "Cleaning stuff up..."
    trap '' EXIT INT TERM
    exit $err 
}
sig_cleanup() {
    trap '' EXIT # some shells will call EXIT after the INT handler
    false # sets $?
    cleanup
}
trap cleanup EXIT
trap sig_cleanup INT QUIT TERM

Puntos anteriores:

Los controladores INT y TERM no se dan por vencidos cuando pruebo: manejan el error y el shell vuelve a salir (y esto no es demasiado sorprendente). Por lo tanto, me aseguro de que la limpieza salga después, y en el caso de las señales siempre usa un código de error (y en el otro caso de una salida normal, conserva el código de error).

Con bash, parece que salir en el controlador INT también llama al controlador EXIT, por lo tanto, destapo el controlador de salida y lo llamo yo mismo (que funcionará en cualquier shell independientemente del comportamiento).

Atrapo la salida porque los scripts de shell pueden salir antes de llegar al fondo: errores de sintaxis, set -e y un retorno distinto de cero, simplemente llamando a exit. No puede confiar en que un shellscript llegue al fondo.

SIGQUIT es Ctrl- \ si nunca lo has probado. Obtiene un coredump adicional. Así que creo que también vale la pena atraparlo, incluso si es un poco oscuro.

La experiencia pasada dice que si (como yo) siempre presiona Ctrl-C varias veces, a veces lo atrapará a la mitad de la parte de limpieza de su script de shell, por lo que esto funciona, pero no siempre tan perfectamente como le gustaría.

ijw
fuente
2
La persona que llama simplemente obtener 1 como el código de salida, no importa lo que provocó la salida de la señal, mientras que sin que trapla persona que llama obtendría 130 para SIGINT, 143 para SIGTERM, etc. Así que me gustaría capturar y transmitir el código de salida correcto como: sig_cleanup() { err=$?; trap '' EXIT; (exit $err); cleanup; }.
musiphil
2
¿Puedes aclarar el propósito de trap '' EXIT INT TERMla función de limpieza? ¿Es esto para evitar que el usuario interrumpa accidentalmente la limpieza que mencionó en el último párrafo? ¿No es EXITredundante?
Seis