Lo que pasa es que tanto bashy pingreciben el SIGINT ( bashsiendo no interactiva, tanto pingy bashejecutarse 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, bashmaneja ese SIGINT de forma asíncrona, solo después de que el comando actualmente en ejecución haya salido. bashsolo 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, shy sleeprecibir SIGINT al pulsar Ctrl-C, pero shlas salidas normalmente con un código de salida de 0, por lo que bashignora 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 pingestá ejecutando, las bashnotas que presionó Ctrl-Cen sus controladores SIGINT, pero como pingsale normalmente, bashno salen.
Si agrega un sleep 1en ese bucle y presiona Ctrl-Cmientras se sleepestá ejecutando, porque sleepno tiene un controlador especial en SIGINT, morirá e informará bashque murió de un SIGINT, y en ese caso bashsaldrá (en realidad se suicidará con SIGINT como para informar la interrupción a su padre).
En cuanto a por qué se bashcomporta así, no estoy seguro y noto que el comportamiento no siempre es determinista. Acabo de hacer la pregunta en la bashlista 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 ksh93sale cada vez que un comando es asesinado por SIGINT.
Obtiene el mismo comportamiento que el bashanterior 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 bashsalida 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 bashscript, por ejemplo, ese bashscript también se interrumpe). Hacer un exit 130no 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, ksh93o 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,visolo 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 elforbucle como ejemplo).