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).vi
(bueno,vim
al menos) deshabilita ttyisig
cuando edita (aunque obviamente no lo hace cuando ejecuta:!cmd
, y eso se aplicaría mucho en ese caso).ping
sale con 0 después de recibir SIGINT. Encontré un comportamiento similar cuando un script bash contiene ensudo
lugar deping
, perosudo
sale 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 comosleep
llamadas 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
ping
regresa con un estado de salida 0 al recibir SIGINT y quebash
luego 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
INT
señal al grupo de procesos en primer plano, que aquí incluye el shell, ya queping
no 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á.
(
INT
Manejo 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
ping
no 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 1
script de bash y ¡bang!Ahora puedo detenerlo con dos Ctrl+C.
Al presionar Ctrl+C,
SIGINT
se 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 dosSIGINT
señales, una para interrumpir el comando actual en ejecución y otra para interrumpir el proceso de subshell .En el script sin la
sleep
llamada, 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á unSIGINT
a 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ónfirefox
y guardará este PID en un archivo llamadoany_file_name
. El comando 'sed' agregará alkill
comienzo del número PID en el archivo 'any_file_name'. La tercera líneaany_file_name
archivará 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
bash
funció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
bash
es esa? AFAIKbash
solo 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 feasmake
que mata explícitamente al grupo de procesos para un comando actualmente en ejecución a fin de permitir^C
abortar 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 lazsh
forma de pasar argv [0]).