Utilizo scripts de shell para reaccionar a los eventos del sistema y actualizar las pantallas de estado en mi administrador de ventanas. Por ejemplo, un script determina el estado actual de wifi escuchando múltiples fuentes:
- asociar / disociar eventos de wpa_supplicant
- la dirección cambia de ip (entonces sé cuando dhcpcd ha asignado una dirección)
- un proceso de temporizador (para que la intensidad de la señal se actualice de vez en cuando)
Para lograr la multiplexación, termino generando procesos en segundo plano:
{ wpa_cli -p /var/run/wpa_supplicant -i wlan0 -a echo &
ip monitor address &
while sleep 30; do echo; done } |
while read line; do update_wifi_status; done &
es decir, la configuración es que cada vez que cualquiera de las fuentes de eventos genera una línea, mi estado de wifi se actualiza. Toda la canalización se ejecuta en segundo plano (el '&' final) porque también veo otro origen de eventos que hace que mi script finalice:
wait_for_termination
kill $!
Se supone que kill elimina los procesos en segundo plano, pero de esta forma no hace el trabajo. Los procesos 'wpa_cli' e 'ip' siempre sobreviven, al menos, y tampoco mueren en su próximo evento (en teoría deberían obtener un SIGPIPE; supongo que el proceso de lectura también debe estar vivo).
La pregunta es, ¿cómo limpiar de manera confiable [y elegante!] Todos los procesos en segundo plano generados?
Respuestas:
La solución súper simple es agregar esto al final del script:
kill -- -$$
Explicación:
$$
nos da el PID del shell en ejecución. Entonces,kill $$
enviaría un SIGTERM al proceso de shell. Sin embargo, si negamos el PID,kill
envía una SIGTERM a cada proceso en el grupo de procesos . Necesitamos la información--
previa parakill
saber que-$$
es una ID de grupo de proceso y no una bandera.¡Tenga en cuenta que esto depende de que el shell en ejecución sea un líder de grupo de procesos! De lo contrario,
$$
(el PID) no coincidirá con la ID del grupo de proceso, y terminará enviando una señal a quién sabe dónde (bueno, probablemente en ninguna parte, ya que es poco probable que haya un grupo de proceso con una ID coincidente si no somos un grupo líder).Cuando se inicia el shell, crea un nuevo grupo de procesos [1]. Cada proceso bifurcado se convierte en miembro de ese grupo de procesos, a menos que cambien explícitamente su grupo de procesos a través de syscall (
setpgid
).La forma más sencilla de garantizar un script en particular se ejecuta como un líder de grupo del proceso es poner en marcha usando
setsid
. Por ejemplo, tengo algunos de estos scripts de estado que ejecuto desde un script principal:Escrito de esta manera, los scripts de wifi y batería se ejecutan con el mismo grupo de procesos que el script principal, y
kill -- -$$
no funciona. La solución es:Encontré
pstree -p -g
útil para visualizar el proceso y las ID de grupo de proceso.Gracias a todos los que contribuyeron y me hicieron profundizar un poco, ¡aprendí cosas! :)
[1] ¿Hay otras circunstancias en las que el shell crea un grupo de procesos? p.ej. al iniciar una subshell? no lo sé...
fuente
kill -- -$$
También se mata? Estoy interesado en hacer un guión que pueda matar a sus hijos pero no a sí mismo.jobs
). Si los niños también pueden bifurcar, aún puede encontrar esta técnica útil. es decir. usesetsid
para que cada proceso secundario se convierta en un líder de grupo y luegokill -- -$CHILD
para matar al menor y a sus descendientes.OK, se me ocurrió una solución bastante decente que no usa cgroups. No funcionará frente a los procesos de bifurcación, como señaló Leonardo Dagnino.
Uno de los problemas con el seguimiento manual de las ID de proceso a través de $! matarlos más tarde es la condición de carrera inherente: si el proceso finaliza antes de matarlo, el script enviará una señal a un proceso inexistente o posiblemente incorrecto.
Podemos verificar la finalización del proceso dentro del shell a través del sistema de espera incorporado, pero solo podemos esperar la finalización de todos los procesos en segundo plano o de un solo pid. En ambos casos, espere bloques, lo que lo hace inadecuado para la tarea de verificar si un PID determinado aún se está ejecutando.
Al buscar una solución a lo anterior, me topé con el comando jobs , que anteriormente pensaba que solo estaba disponible para shells interactivos. Resulta que funciona bien sus scripts, y realiza un seguimiento automático de los procesos en segundo plano que hemos lanzado; si un proceso ha finalizado, ya no aparecerá en la lista de trabajos.
Por lo tanto, el comando:
es suficiente para garantizar, en casos simples, la finalización de los procesos en segundo plano cuando sale el shell actual.
En mi caso, uno no es suficiente, porque también estoy iniciando el proceso en segundo plano desde una subshell, y se eliminan las trampas para cada nueva subshell. Entonces, necesito hacer la misma trampa dentro de la subshell:
Finalmente, jobs -p solo da el pid del último proceso en la tubería (¡como $!). Como puede ver, estoy generando procesos en segundo plano en el primer proceso de la canalización en segundo plano, por lo que también quiero señalar ese pid.
El primer proceso 'pid se puede obtener de los trabajos , pero no estoy seguro de cómo se puede lograr esto. Usando bash, obtengo resultados en este estilo:
Entonces, al usar un comando kill ligeramente modificado del script principal, puedo señalar todos los procesos en la tubería:
fuente
jobs -p
da el PID del primer proceso en una tubería, el líder del grupo de procesos, no el último proceso. En segundo lugar, tambiénjobs -l
enumera los PID de procesos eliminados o salidos, por lo que su posible condición de carrera está allí nuevamente. ¿Por qué no poner la tubería en un verdadero subshell, abierto con(...)
?jobs -p
devuelve el PID de esta subshell. O use elps
cual, en la columna PPID, enumera los PID principales (es decir, su$$
). Tenga en cuenta también la$PPID
variable Bash.ps
es la utilidad grotescamente hinchada que es GNUps
, que nunca he aprendido a usar bien para crear secuencias de comandos a favor de herramientas más sanas. Sin embargo, parece un buen enfoque ... Para mi caso, parece quepgrep -g $$
me da exactamente lo que quiero (PID de todos los procesos que pertenecen al mismo grupo de procesos que $$) ... Simplemente no estoy 100 $ seguro de dónde el límite del "grupo de procesos" es.setpgid()
cuando se inicia para crear un nuevo grupo de procesos, y puede usar el antiguo normalkill
para señalar un grupo de procesos usando un número negativo. Entonces ...kill -$$
debería matar todas las tareas en segundo plano, siempre y cuando ninguna de ellas haya comenzado su propio grupo de procesos :)Puede obtener el PID de cada uno poniendo algo como
después del wpa_cli,
después de la ip, y al final del script los matas a ambos:
Sin embargo, eso no funcionará si los procesos se bifurcan. Entonces cgroups sería la mejor solución.
fuente