¿Mantener códigos de salida al atrapar SIGINT y similares?

14

Si uso trapcomo se describe, por ejemplo, en http://linuxcommand.org/wss0160.php#trap para capturar ctrl-c (o similar) y limpiar antes de salir, entonces estoy cambiando el código de salida devuelto.

Ahora, esto probablemente no hará la diferencia en el mundo real (por ejemplo, porque los códigos de salida no son portátiles y, además, no siempre son inequívocos, como se discute en Código de salida predeterminado cuando finaliza el proceso ), pero todavía me pregunto si hay ¿Realmente no hay forma de evitar eso y devolver el código de error predeterminado para los scripts interrumpidos?

Ejemplo (en bash, pero mi pregunta no debe considerarse específica de bash):

#!/bin/bash
trap 'echo EXIT;' EXIT
read -p 'If you ctrl-c me now my return code will be the default for SIGTERM. ' _
trap 'echo SIGINT; exit 1;' INT
read -p 'If you ctrl-c me now my return code will be 1. ' _

Salida:

$ ./test.sh # doing ctrl-c for 1st read
If you ctrl-c me now my return code will be the default for SIGTERM.
$ echo $?
130
$ ./test.sh # doing ctrl-c for 2nd read
If you ctrl-c me now my return code will be the default for SIGTERM.
If you ctrl-c me now my return code will be 1. SIGINT 
EXIT
$ echo $?
1

(Editado para eliminar para que sea más compatible con POSIX).

(Editado nuevamente para convertirlo en un script bash, mi pregunta no es específica de shell).

Editado para usar el "INT" portátil para la trampa en favor del "SIGINT" no portátil.

Editado para eliminar llaves inútiles y agregar una solución potencial.

Actualizar:

Lo resolví ahora simplemente saliendo con algunos códigos de error codificados y atrapando EXIT. Esto puede ser problemático en ciertos sistemas porque el código de error puede diferir o la trampa EXIT no es posible, pero en mi caso está bien.

trap cleanup EXIT
trap 'exit 129' HUP
trap 'exit 130' INT
trap 'exit 143' TERM
phk
fuente
Su secuencia de comandos se ve un poco extraña: le dice readque lea desde el coproceso actual y trap cmd SIGINTno funcionará como el estándar dice que debe usar trap cmd INT.
schily
Ah sí, en POSIX, por supuesto, no tiene el prefijo SIG.
phk
Vaya, pero luego "read -p" tampoco sería compatible, así que voy a adaptarlo para bash.
phk
@schily: No sé a qué te refieres con "coproceso".
phk
Bueno, la página del manual de Korn Shell dice que read -plee las entradas del coproceso actual.
schily

Respuestas:

4

Todo lo que necesita hacer es cambiar el controlador EXIT dentro de su controlador de limpieza. Aquí hay un ejemplo:

#!/bin/bash
cleanup() {
    echo trapped exit
    trap 'exit 0' EXIT
}
trap cleanup EXIT
read -p 'If you ctrl-c me now my return code will be the default for SIGTERM. '
rocoso
fuente
¿quiso decir en trap cleanup INTlugar de trap cleanup EXIT?
Jeff Schaller
Creo que quise decir SALIR. Cuando finalmente se llama a exit, como sea que esté hecho, cambiamos la trampa para salir con return 0. No creo que los manejadores de señales sean recursivos.
rocoso
OK, en su ejemplo, no estoy atrapando (SIG) INT en absoluto, lo que en realidad es lo que quiero para hacer un poco de limpieza, incluso si el usuario está saliendo de mi script interactivo a través de ctrl-c.
phk
@phk Ok. Aunque creo que tienes la idea de cómo forzar la salida para devolver 0 o algún otro valor, lo que reuní fue el problema. Supongo que podrá ajustar el código para poner la configuración EXIT de la trampa dentro de su controlador de limpieza real que SIGINT llama.
rocoso
@rocky: Mi objetivo era tener un controlador de trampa sin cambiar el código de salida que normalmente tendría sin el controlador. Ahora podría simplemente devolver el código de salida que normalmente obtendría, pero el problema era que no sé el código de salida exacto en estos casos (como se ve en el hilo al que me vinculé y la publicación de meuh es bastante complicado). Su publicación aún fue útil, no pensé en redefinir el controlador de trampa dinámicamente.
phk
9

En realidad, la interrupción interna de bash readparece ser un poco diferente a la interrupción de un comando ejecutado por bash. Normalmente, cuando ingresas trap, $?está configurado y puedes conservarlo y salir con el mismo valor:

trap 'rc=$?; echo $rc SIGINT; exit $rc' INT
trap 'rc=$?; echo $rc EXIT; exit $rc' EXIT

Si su secuencia de comandos se interrumpe al ejecutar un comando como sleep o incluso como incorporado wait, verá

130 SIGINT
130 EXIT

y el código de salida es 130. Sin embargo, read -pparece que $?es 0 (en mi versión de bash 4.3.42 de todos modos).


El manejo de señales durante el readtrabajo podría estar en progreso, de acuerdo con el archivo de cambios en mi versión ... (/ usr / share / doc / bash / CHANGES)

cambios entre esta versión, bash-4.3-alpha y la versión anterior, bash-4.2-release.

  1. Nuevas características en Bash

    r. Cuando está en modo Posix, 'leer' es interrumpible por una señal atrapada. Después de ejecutar el controlador de trampa, leer devuelve una señal de 128 + y desecha cualquier entrada parcialmente leída.

meuh
fuente
OK, eso es realmente extraño. Es 130 en el shell interactivo en bash 4.3.42 (bajo cygwin), 0 bajo el mismo shell cuando está en un script y en modo POSIX o no hace ninguna diferencia. Pero luego, debajo del tablero y busybox siempre es 1.
phk
Probé el modo POSIX con una trampa que no sale y reinicia read, como se indica en el archivo CAMBIOS (agregado a mi respuesta). Entonces, podría ser un trabajo en progreso.
meuh
El código de salida 130 es 100% no portátil. Mientras Bourne Shell usa 128 + signocomo código de salida para las señales, ksh93 usa 256 + signo. POSIX dice: algo por encima de 128 ...
schily
@schily True, como se señaló en el hilo al que me vinculé en la publicación original ( unix.stackexchange.com/questions/99112 ).
phk
6

Cualquier código de salida de señal habitual estará disponible $?al ingresar al controlador de trampa:

sig_handler() {
    exit_status=$?  # Eg 130 for SIGINT, 128 + (2 == SIGINT)
    echo "Doing signal-specific up"
    exit "$exit_status"
}
trap sig_handler INT HUP TERM QUIT

Si hay una trampa de SALIDA separada, puede usar la misma metodología: mantenga inmediatamente el estado de salida pasado por la limpieza del controlador de señal (si hay una), luego devuelva el estado de salida guardado.

Tom Hale
fuente
Esto se aplica a la mayoría de las conchas? Muy agradable. localaunque no es POSIX.
phk
1
Editado No he probado en otro que {ba,z}sh. AFAIK, es lo mejor que se puede hacer, independientemente.
Tom Hale
Esto no funciona en el tablero ( $?es 1 cualquiera que sea la señal recibida).
MoonSweep
2

Simplemente devolver algún código de error no es suficiente para simular una salida de SIGINT. Me sorprende que nadie haya mencionado esto hasta ahora. Lectura adicional: https://www.cons.org/cracauer/sigint.html

La forma correcta es:

for sig in EXIT ABRT HUP INT PIPE QUIT TERM; do
    trap "cleanup;
          [ $sig  = EXIT ] && normal_exit_only_cleanup;
          [ $sig != EXIT ] && trap - $sig EXIT && kill -s $sig $$
         " $sig
done

Esto funciona con Bash, Dash y zsh. Para una mayor portabilidad, debe usar especificaciones de señal numérica (por otro lado, zsh espera un parámetro de cadena para el killcomando ...)

También tenga en cuenta el tratamiento especial de la EXITseñal. Esto se debe a que algunos shells (es decir, Bash) también ejecutan trampas EXITpara cualquier señal (después de posibles trampas definidas en esa señal). El reinicio de la EXITtrampa lo impide.

Ejecutar código solo en la salida "normal"

La verificación [ $sig = EXIT ]permite ejecutar código solo en la salida normal (no señalizada). Sin embargo, todas las señales tienen que tener trampas que finalmente reinicien la trampa EXIT; normal_exit_only_cleanupTambién se llamará para las señales que no lo hacen. También se ejecutará por una salida a través set -e. Esto se puede solucionar atrapándolo ERR(que no es compatible con Dash) y agregando una marca de verificación [ $sig = ERR ]antes de kill.

Versión simplificada de solo Bash

Por otro lado, este comportamiento significa que en Bash simplemente puedes hacer

trap cleanup EXIT

simplemente ejecutar un código de limpieza y retener el estado de salida.

editado

  • El comportamiento elaborado de Bash de "EXIT atrapa todo"

  • Eliminar la señal KILL, que no puede quedar atrapada

  • Eliminar los prefijos SIG de los nombres de señal

  • No intentes kill -s EXIT

  • Tomar set -e/ ERR en cuenta

philipp2100
fuente
No existe un requisito general de que un programa que atrapa señales termine de una manera que conserve el hecho de que recibió una señal. Por ejemplo, un programa puede declarar que el envío SIGINTes la forma de cerrarlo y puede decidir salir con 0 si logró terminar sin error u otro código de error si no se cerró limpiamente. El caso en cuestión: top. Ejecuta top; echo $?y luego presiona Ctrl-C. El estado volcado en la pantalla será 0.
Louis
1
Gracias por señalar eso. Sin embargo, el póster preguntó específicamente sobre mantener el código de salida.
philipp2100