
Tengo un programa que ignora SIGINT pero quiero ejecutarlo en primer plano. Me gustaría encontrar una manera de forzar su cierre con Ctrl-C. ¿Hay alguna forma de escribir un contenedor (al que usted llamaría ./wrapper.sh my_program
) que obligaría a cerrar el programa que se comporta mal, potencialmente detectando el SIGINT ignorado y generando un SIGKILL?
esta respuestaes exactamente lo contrario de lo que estoy buscando: me gustaría forzar a un programa que ignora una señal a salir de SIGINT.
Respuesta1
Construyamos un contenedor que "convierta" SIGINT en SIGKILL.
Necesitamos un proceso que se ejecute my_program
y obtenga SIGINT en Ctrl+ c. Cuando recibe SIGINT debe enviar SIGKILL a my_program
. Tenga en cuenta que no necesitamos my_program
recibir SIGINT causado por Ctrl+ c; es suficiente que obtenga SIGKILL causado por el proceso adicional.
Hechos relevantes:
- Una forma estándar de ejecutar un proceso junto con otro es ejecutar uno de ellos de forma asincrónica en segundo plano (es decir, terminando
&
). - Cuando presionas Ctrl+ c, es tu emulador de terminal quien envía SIGINT a los procesos en el grupo de procesos de primer plano.
- Algunos shells (simples) ejecutan todo en el mismo grupo de procesos. Si se les dice que ejecuten un comando en segundo plano, redirigirán su entrada estándar a
/dev/null
un archivo equivalente para evitar que el comando robe la entrada. - Otros shells pueden ejecutar cada comando en un grupo de procesos separado. Si se les dice que ejecuten un comando en segundo plano, dejarán su entrada estándar como está; Aún así, el comando en segundo plano no podrá robar la entrada del terminal de control debido a
SIGTTIN
. Esto permite que el shell mueva un trabajo del fondo al primer plano informando al terminal del nuevo grupo de procesos en primer plano. El mecanismo se llama control de trabajo y se puede desactivar. En los scripts está deshabilitado de forma predeterminada. - De cualquier manera, un proceso en segundo plano no puede leer desde la terminal. Esto significa que no debemos ejecutar
my_program
en segundo plano en caso de que la entrada estándar sea la terminal ymy_program
necesite leer desde ella. - Desafortunadamente, en algunos shells tampoco deberíamos ejecutar el otro proceso en segundo plano. Algunos shells utilizan grupos de procesos separados incluso cuando el control de trabajos está deshabilitado. El otro proceso que no esté en el grupo de procesos de primer plano no recibirá SIGINT en Ctrl+ c, por lo que no podrá "convertirlo" a SIGKILL.
- En mi Debian 10
posh
hay un shell que ejecuta todo en el mismo grupo de procesos.
Esto lleva al siguiente contenedor:
#!/usr/bin/env posh
( trap 'kill -s KILL 0' INT
while kill -s 0 "$$" 2>/dev/null; do sleep 1; done
) &
exec "$@"
exec "$@"
se ejecuta my_program
(o lo que usted especifique), posiblemente con argumentos. Gracias a exec
my_program
reemplazará el envoltorio. No solo podrá leer desde la entrada estándar que puede ser una terminal; su PID será el del contenedor reemplazado. En este sentido el envoltorio es transparente. Además, esto tiene una característica interesante: el PID my_program
se conoce antes de my_program
que comience y podemos usarlo fácilmente en el otro proceso (en este caso en un subshell en segundo plano) para detectar cuándo my_program
termina realmente.
El trap
"convierte" SIGINT en SIGKILL. Note kill -s KILL 0
envía SIGKILL a todo el grupo de procesos, incluidos los hijos de my_program
si están en el grupo (si desea eliminar my_program
solo, utilícelo kill -s KILL "$$"
en su lugar). Sin embargo kill -s 0 "$$"
prueba la existencia de my_program
sólo.
Existe una alternativa que no requiere un shell que ejecute todo en el mismo grupo de procesos. El truco es: los procesos de una canalización deben ejecutarse en un grupo de procesos; y mediante una redirección inteligente se puede construir un canal donde las piezas no estén conectadas entre sí.
#!/bin/sh -
exec 9>&1
( "$@"; kill -s TERM 0 ) >&9 9>&- | (
trap 'kill -s KILL 0' INT
while :; do sleep 1; done
)
En esta variante my_program
no se sustituirá el envoltorio. El segundo kill
es "convertir" SIGINT a SIGKILL. La primera kill
es terminar el bucle en caso de my_program
salidas en circunstancias en las que, de otro modo, el bucle sobreviviría.
Hay algunos escenarios que pueden no funcionar como se espera (con cualquier contenedor). Entre ellos:
- Si
my_program
se bifurca y sale, dejando el verdadero trabajo al niño. Lo más probable es que el programa problemático en cuestión no se comporte así, ya que intentaste Ctrl+ c. Pero en general podría ser así. - Si
my_program
genera procesos secundarios en otros grupos de procesos y desea eliminarlos junto con sus padres. - Si
my_program
configura el terminal para no enviar SIGINT en Ctrl+ c. Si sospecha que esto sucede, mejore el contenedor poniendostty -F /dev/tty intr ^C
entresleep 1
ydone
. En este caso, es posible que el programa ni siquiera ignore SIGINT, simplemente puede asegurarse de que no lo obtendrá del terminal. Entonces tal vez SIGKILL sea excesivo; tal vez sea suficiente con restaurar esta funcionalidad del terminal y Ctrl+ ccomenzará a funcionar.
De un comentario:
Los programas que ignoran SIGINT normalmente lo hacen por una muy buena razón.
Verdadero. Pruebe SIGTERM o SIGHUP en lugar de SIGKILL. Quizás el programa en cuestión no ignore al menos uno de estos y salga correctamente con una limpieza adecuada.