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).vi(bueno,vimal menos) deshabilita ttyisigcuando edita (aunque obviamente no lo hace cuando ejecuta:!cmd, y eso se aplicaría mucho en ese caso).pingsale con 0 después de recibir SIGINT. Encontré un comportamiento similar cuando un script bash contiene ensudolugar deping, perosudosale con 1 después de recibir SIGINT. unix.stackexchange.com/questions/479023/…La explicación es que bash implementa WCE (espera y salida cooperativa) para SIGINT y SIGQUIT según http://www.cons.org/cracauer/sigint.html . Eso significa que si bash recibe SIGINT o SIGQUIT mientras espera que salga un proceso, esperará hasta que el proceso salga y se cerrará si el proceso salió de esa señal. Esto asegura que los programas que usan SIGINT o SIGQUIT en su interfaz de usuario funcionarán como se esperaba (si la señal no hizo que el programa terminara, el script continuará normalmente).
Una desventaja aparece con los programas que capturan SIGINT o SIGQUIT pero luego terminan por eso, pero usando una salida normal () en lugar de reenviar la señal a ellos mismos. Es posible que no sea posible interrumpir los scripts que llaman a dichos programas. Creo que la solución real que hay en programas como ping y ping6.
Comportamiento similar es implementado por ksh93 y FreeBSD's / bin / sh, pero no por la mayoría de los otros shells.
fuente
exit(130)por ejemplo si interrumpemksh -c 'sleep 10;:').Como supones, esto se debe a que SIGINT se está enviando al proceso subordinado y al shell que continúa después de que ese proceso finaliza.
Para manejar esto de una mejor manera, puede verificar el estado de salida de los comandos que se están ejecutando. El código de retorno de Unix codifica tanto el método por el cual salió un proceso (llamada o señal del sistema) como a qué valor se pasó
exit()o qué señal finalizó el proceso. Todo esto es bastante complicado, pero la forma más rápida de usarlo es saber que un proceso que finalizó por señal tendrá un código de retorno distinto de cero. Por lo tanto, si verifica el código de retorno en su secuencia de comandos, puede salir si el proceso secundario finalizó, eliminando la necesidad de inelegitudes comosleepllamadas innecesarias . Una forma rápida de hacer esto a través de su script es usarloset -e, aunque puede requerir algunos ajustes para los comandos cuyo estado de salida es distinto de cero.fuente
pingregresa con un estado de salida 0 al recibir SIGINT y quebashluego ignora el SIGINT que recibió si ese es el caso. Agregar un "set -e" o verificar el estado de salida no ayudará aquí. Agregar una trampa explícita en SIGINT ayudaría.El terminal nota el control-c y envía una
INTseñal al grupo de procesos en primer plano, que aquí incluye el shell, ya quepingno ha creado un nuevo grupo de procesos en primer plano. Esto es fácil de verificar atrapandoINT.Si el comando que se está ejecutando ha creado un nuevo grupo de procesos en primer plano, entonces el control-c irá a ese grupo de procesos y no al shell. En ese caso, el shell deberá inspeccionar los códigos de salida, ya que el terminal no lo indicará.
(
INTManejo de conchas puede ser complicado fabulosamente, por cierto, ya que la cáscara a veces necesita hacer caso omiso de la señal, ya veces no Fuente de buceo si curioso, o Ponder:.tail -f /etc/passwd; echo foo)fuente
pingno tiene ninguna razón para comenzar un nuevo grupo de procesos aquí y la versión de ping (iputils en Debian) con la que puedo reproducir el problema del OP no crea un grupo de procesos.Bueno, traté de agregar un
sleep 1script de bash y ¡bang!Ahora puedo detenerlo con dos Ctrl+C.
Al presionar Ctrl+C,
SIGINTse envía una señal al proceso ejecutado actualmente, cuyo comando se ejecutó dentro del bucle. Luego, el proceso de subshell continúa ejecutando el siguiente comando en el ciclo, que inicia otro proceso. Para poder detener el script, es necesario enviar dosSIGINTseñales, una para interrumpir el comando actual en ejecución y otra para interrumpir el proceso de subshell .En el script sin la
sleepllamada, presionar Ctrl+Cmuy rápido y muchas veces no parece funcionar, y no es posible salir del ciclo. Supongo que presionar dos veces no es lo suficientemente rápido como para hacerlo justo en el momento correcto entre la interrupción del proceso ejecutado actual y el inicio del siguiente. Cada Ctrl+Cpulsación enviará unSIGINTa un proceso ejecutado dentro del bucle, pero tampoco a la subshell .En el script con
sleep 1, esta llamada suspenderá la ejecución por un segundo, y cuando se interrumpe por el primero Ctrl+C(primeroSIGINT), la subshell tardará más tiempo en ejecutar el siguiente comando. Así que ahora, el segundo Ctrl+C(segundoSIGINT) irá a la subshell y finalizará la ejecución del script.fuente
Prueba esto:
Ahora cambie la primera línea a:
e intente nuevamente: vea si el ping ahora es interrumpible.
fuente
por ejemplo
pgrep -f firefox, grep el PID de ejecuciónfirefoxy guardará este PID en un archivo llamadoany_file_name. El comando 'sed' agregará alkillcomienzo del número PID en el archivo 'any_file_name'. La tercera líneaany_file_namearchivará el ejecutable. Ahora la cuarta línea eliminará el PID disponible en el archivoany_file_name. Escribir las cuatro líneas anteriores en un archivo y ejecutar ese archivo puede hacer el Control- C. Trabajando absolutamente bien para mí.fuente
Si alguien está interesado en una solución para esta
bashfunción, y no tanto en la filosofía detrás de esto , aquí hay una propuesta:No ejecute el comando problemático directamente, pero desde un contenedor que a) espera a que termine b) no se mete con las señales yc) no implementa el mecanismo WCE en sí, sino que simplemente muere al recibir a
SIGINT.Tal contenedor podría hacerse con
awk+ susystem()función.Poner en un script como OP:
fuente
Eres víctima de un conocido error de bash. Bash hace control de trabajo para los scripts, lo cual es un error.
Lo que sucede es que bash ejecuta los programas externos en un grupo de procesos diferente al que usa para el script en sí. A medida que el grupo de proceso TTY se establece en el grupo de proceso del proceso en primer plano actual, solo este proceso en primer plano se anula y el bucle en el script de shell continúa.
Para verificar: Obtenga y compile un Bourne Shell reciente que implemente pgrp (1) como un programa integrado, luego agregue / bin / sleep 100 (o / usr / bin / sleep dependiendo de su plataforma) al bucle del script y luego inicie Bourne Shell. Después de usar ps (1) para obtener las ID de proceso para el comando sleep y el bash que ejecuta el script, llame
pgrp <pid>y reemplace "<pid>" por el ID del proceso del sleep y el bash que ejecuta el script. Verá diferentes ID de grupo de proceso. Ahora llame a algo comopgrp < /dev/pts/7(reemplace el nombre tty por el tty utilizado por el script) para obtener el grupo de proceso tty actual. El grupo de proceso TTY es igual al grupo de proceso del comando de suspensión.Para solucionarlo: use un shell diferente.
Las fuentes recientes de Bourne Shell están en mi paquete de herramientas schily que puedes encontrar aquí:
http://sourceforge.net/projects/schilytools/files/
fuente
bashes esa? AFAIKbashsolo hace eso si pasa la opción -m o -i.bash -c 'ps -j; ps -j; ps -j')./bin/sh -ce. Tuve que agregar una solución feasmakeque mata explícitamente al grupo de procesos para un comando actualmente en ejecución a fin de permitir^Cabortar una llamada en capas. ¿Verificó si bash cambió el grupo de procesos desde el id del grupo de procesos con el que se inició?ARGV0=sh bash -ce 'ps -j; ps -j; ps -j'informa el mismo pgid para ps y bash en todas las invocaciones de 3 ps. (ARGV0 = sh es lazshforma de pasar argv [0]).