Lo que pasa es que tanto bash
y ping
reciben el SIGINT ( bash
siendo no interactiva, tanto ping
y bash
ejecutarse en el mismo grupo de procesos que se ha creado y se establece como grupo de procesos en primer plano de la terminal por el intérprete de comandos interactivo que ejecutó el guión de).
Sin embargo, bash
maneja ese SIGINT de forma asíncrona, solo después de que el comando actualmente en ejecución haya salido. bash
solo sale al recibir ese SIGINT si el comando actualmente en ejecución muere de un SIGINT (es decir, su estado de salida indica que SIGINT lo ha matado).
$ bash -c 'sh -c "trap exit\ 0 INT; sleep 10; :"; echo here'
^Chere
Por encima, bash
, sh
y sleep
recibir SIGINT al pulsar Ctrl-C, pero sh
las salidas normalmente con un código de salida de 0, por lo que bash
ignora el SIGINT, que es por eso que vemos "aquí".
ping
, al menos el de iputils, se comporta así. Cuando se interrumpe, imprime estadísticas y sale con un estado de salida 0 o 1 dependiendo de si se respondieron o no sus pings. Entonces, cuando presiona Ctrl-C mientras se ping
está ejecutando, las bash
notas que presionó Ctrl-C
en sus controladores SIGINT, pero como ping
sale normalmente, bash
no salen.
Si agrega un sleep 1
en ese bucle y presiona Ctrl-C
mientras se sleep
está ejecutando, porque sleep
no tiene un controlador especial en SIGINT, morirá e informará bash
que murió de un SIGINT, y en ese caso bash
saldrá (en realidad se suicidará con SIGINT como para informar la interrupción a su padre).
En cuanto a por qué se bash
comporta así, no estoy seguro y noto que el comportamiento no siempre es determinista. Acabo de hacer la pregunta en la bash
lista de correo de desarrollo ( Actualización : @Jilles ahora ha aclarado la razón en su respuesta ).
El único otro shell que encontré que se comporta de manera similar es ksh93 (Actualización, como lo menciona @Jilles, también lo hace FreeBSDsh
). Allí, SIGINT parece ser simplemente ignorado. Y ksh93
sale cada vez que un comando es asesinado por SIGINT.
Obtiene el mismo comportamiento que el bash
anterior pero también:
ksh -c 'sh -c "kill -INT \$\$"; echo test'
No emite "prueba". Es decir, sale (al suicidarse con SIGINT allí) si el comando que estaba esperando muere de SIGINT, incluso si no recibió ese SIGINT.
Una solución sería agregar un:
trap 'exit 130' INT
En la parte superior de la secuencia de comandos para forzar la bash
salida al recibir un SIGINT (tenga en cuenta que, en cualquier caso, SIGINT no se procesará de forma síncrona, solo después de que el comando actualmente en ejecución haya salido).
Idealmente, nos gustaría informar a nuestros padres que morimos de un SIGINT (de modo que si se trata de otro bash
script, por ejemplo, ese bash
script también se interrumpe). Hacer un exit 130
no es lo mismo que morir de SIGINT (aunque algunos proyectiles se establecerán $?
en el mismo valor para ambos casos), sin embargo, a menudo se usa para informar una muerte por SIGINT (en sistemas donde SIGINT es 2, que es el más).
Sin embargo bash
, ksh93
o FreeBSD sh
, eso no funciona. SIGINT no considera el estado de salida 130 como una muerte y un script principal no abortaría allí.
Entonces, una alternativa posiblemente mejor sería matarnos con SIGINT al recibir SIGINT:
trap '
trap - INT # restore default INT handler
kill -s INT "$$"
' INT
for f in *.txt; do vi "$f"; cp "$f" newdir; done
. Si el usuario escribe Ctrl + C mientras edita uno de los archivos,vi
solo muestra un mensaje. Parece razonable que el ciclo continúe después de que el usuario termine de editar el archivo. (Y sí, sé que se podría decirvi *.txt; cp *.txt newdir
; solo estoy enviando elfor
bucle como ejemplo).