enviar - ¿Cómo elimino procesos/trabajos en segundo plano cuando sale mi script de shell?
ver procesos activos unix (11)
trap ''kill $ (jobs -p)'' EXIT
Haría solo pequeños cambios en la respuesta y uso de trabajos de Johannes -pr para limitar los procesos kill a ejecutar y agregar unas cuantas señales más a la lista:
trap ''kill $(jobs -pr)'' SIGINT SIGTERM EXIT
Estoy buscando una manera de limpiar el desorden cuando sale mi guión de nivel superior.
Especialmente si quiero usar set -e
, deseo que el proceso de fondo muera cuando la secuencia de comandos se cierre.
Así que script la carga del script. Ejecute un comando killall
(o lo que esté disponible en su sistema operativo) que se ejecute tan pronto como finalice el script.
Esto funciona para mí (mejorado gracias a los comentaristas):
trap "trap - SIGTERM && kill -- -$$" SIGINT SIGTERM EXIT
Hice una adaptación de la respuesta de @ tokland combinada con el conocimiento de http://veithen.github.io/2014/11/16/sigterm-propagation.html cuando noté que la trap
no se dispara si estoy ejecutando un primer plano proceso (no se basa en &
):
#!/bin/bash
# killable-shell.sh: Kills itself and all children (the whole process group) when killed.
# Adapted from http://.com/a/2173421 and http://veithen.github.io/2014/11/16/sigterm-propagation.html
# Note: Does not work (and cannot work) when the shell itself is killed with SIGKILL, for then the trap is not triggered.
trap "trap - SIGTERM && echo ''Caught SIGTERM, sending SIGTERM to process group'' && kill -- -$$" SIGINT SIGTERM EXIT
echo $@
"$@" &
PID=$!
wait $PID
trap - SIGINT SIGTERM EXIT
wait $PID
Ejemplo de que funciona:
$ bash killable-shell.sh sleep 100
sleep 100
^Z
[1] + 31568 suspended bash killable-shell.sh sleep 100
$ ps aux | grep "sleep"
niklas 31568 0.0 0.0 19640 1440 pts/18 T 01:30 0:00 bash killable-shell.sh sleep 100
niklas 31569 0.0 0.0 14404 616 pts/18 T 01:30 0:00 sleep 100
niklas 31605 0.0 0.0 18956 936 pts/18 S+ 01:30 0:00 grep --color=auto sleep
$ bg
[1] + 31568 continued bash killable-shell.sh sleep 100
$ kill 31568
Caught SIGTERM, sending SIGTERM to process group
[1] + 31568 terminated bash killable-shell.sh sleep 100
$ ps aux | grep "sleep"
niklas 31717 0.0 0.0 18956 936 pts/18 S+ 01:31 0:00 grep --color=auto sleep
La trap ''kill 0'' SIGINT SIGTERM EXIT
solución descrita en la respuesta de @ tokland es realmente agradable, pero la última versión de Bash falla con un error de segmentación al usarla. Eso es porque Bash, a partir de la v. 4.3, permite la repetición de la trampa, que en este caso se vuelve infinita:
- el proceso de shell recibe
SIGINT
oSIGTERM
oEXIT
; - la señal queda atrapada, ejecutando
kill 0
, que envíaSIGTERM
a todos los procesos del grupo, incluido el propio shell; - ir a 1 :)
Esto puede solucionarse anulando manualmente la trampa:
trap ''trap - SIGTERM && kill 0'' SIGINT SIGTERM EXIT
La manera más elegante, que permite imprimir la señal recibida y evita los mensajes "Terminated:":
#!/usr/bin/env bash
trap_with_arg() { # from https://.com/a/2183063/804678
local func="$1"; shift
for sig in "$@"; do
trap "$func $sig" "$sig"
done
}
stop() {
trap - SIGINT EXIT
printf ''/n%s/n'' "recieved $1, killing children"
kill -s SIGINT 0
}
trap_with_arg ''stop'' EXIT SIGINT SIGTERM SIGHUP
{ i=0; while (( ++i )); do sleep 0.5 && echo "a: $i"; done } &
{ i=0; while (( ++i )); do sleep 0.6 && echo "b: $i"; done } &
while true; do read; done
UPD : ejemplo mínimo agregado; función de stop
mejorada para evitar las señales innecesarias y ocultar los mensajes "finalizados:" de la salida. ¡Gracias Trevor Boyd Smith por las sugerencias!
Otra opción es que la secuencia de comandos se establezca como líder del grupo de procesos y atrape un killpg en su grupo de procesos al salir.
Para estar seguro, me parece mejor definir una función de limpieza y llamarla desde una trampa:
cleanup() {
local pids=$(jobs -pr)
[ -n "$pids" ] && kill $pids
}
trap "cleanup" INT QUIT TERM EXIT [...]
o evitando la función por completo:
trap ''[ -n "$(jobs -pr)" ] && kill $(jobs -pr)'' INT QUIT TERM EXIT [...]
¿Por qué? Porque simplemente usando trap ''kill $(jobs -pr)'' [...]
uno supone que habrá trabajos en segundo plano en ejecución cuando se señalice la condición de trampa. Cuando no hay trabajos, uno verá el siguiente mensaje (o similar):
kill: usage: kill [-s sigspec | -n signum | -sigspec] pid | jobspec ... or kill -l [sigspec]
porque jobs -pr
está vacío - terminé en esa ''trampa'' (juego de palabras intencionado).
Para limpiar un poco de basura, se puede usar trap
. Puede proporcionar una lista de cosas ejecutadas cuando llega una señal específica:
trap "echo hello" SIGINT
pero también se puede usar para ejecutar algo si el shell sale:
trap "killall background" EXIT
Es un built-in, así que help trap
te dará información (funciona con bash). Si solo quiere matar trabajos en segundo plano, puede hacer
trap ''kill $(jobs -p)'' EXIT
Tenga cuidado con el uso de single ''
, para evitar que el shell sustituya el $()
inmediatamente.
Una buena versión que funciona bajo Linux, BSD y MacOS X. Primero intenta enviar SIGTERM, y si no tiene éxito, mata el proceso después de 10 segundos.
KillJobs() {
for job in $(jobs -p); do
kill -s SIGTERM $job > /dev/null 2>&1 || (sleep 10 && kill -9 $job > /dev/null 2>&1 &)
done
}
TrapQuit() {
# Whatever you need to clean here
KillJobs
}
trap TrapQuit EXIT
Tenga en cuenta que los trabajos no incluyen los procesos de Grand Children.
trabajos -p no funciona en todos los shells si se llama en un sub-shell, posiblemente a menos que su salida sea redirigida a un archivo pero no a un pipe. (Supongo que originalmente estaba destinado solo para uso interactivo).
¿Qué hay de lo siguiente?
trap ''while kill %% 2>/dev/null; do jobs > /dev/null; done'' INT TERM EXIT [...]
La llamada a "trabajos" es necesaria con el tablero de instrumentos de Debian, que no puede actualizar el trabajo actual ("%%") si falta.
trap "exit" INT TERM
trap "kill 0" EXIT
¿Por qué convertir INT
y TERM
para salir? Porque ambos deberían desencadenar kill 0
sin ingresar un bucle infinito.
¿Por qué desencadenar kill 0
en EXIT
? Debido a que las salidas de script normales también deberían disparar kill 0
.
¿Por qué kill 0
? Debido a que las subcapas anidadas también deben ser eliminadas. Esto eliminará todo el árbol de procesos .