Réponses:
Pour nettoyer certains dégâts, trap
peut être utilisé. Il peut fournir une liste de choses exécutées lorsqu'un signal spécifique arrive:
trap "echo hello" SIGINT
mais peut également être utilisé pour exécuter quelque chose si le shell se termine:
trap "killall background" EXIT
C'est une fonction intégrée, help trap
vous donnera donc des informations (fonctionne avec bash). Si vous ne souhaitez supprimer que les jobs d'arrière-plan, vous pouvez le faire
trap 'kill $(jobs -p)' EXIT
Attention à utiliser le single '
, pour éviter que la coque ne se substitue $()
immédiatement.
kill $(jobs -p)
ne fonctionne pas dans le tiret, car il exécute la substitution de commandes dans un sous-shell (voir Substitution de commandes dans le tiret man)
killall background
censé être un espace réservé? background
n'est pas dans la page de manuel ...
Cela fonctionne pour moi (amélioré grâce aux commentateurs):
trap "trap - SIGTERM && kill -- -$$" SIGINT SIGTERM EXIT
4.3.30(1)-release
OSX, et cela est également confirmé sur Ubuntu . Il y a un wokaround obvoius , cependant :)
-$$
. Il évalue à '- <PID> `par exemple -1234
. Dans la page de manuel kill // page de manuel intégrée, un tiret de tête spécifie le signal à envoyer. Cependant - bloque probablement cela, mais le tiret de tête n'est pas documenté autrement. De l'aide?
man 2 kill
, ce qui explique que lorsqu'un PID est négatif, le signal est envoyé à tous les processus du groupe de processus avec l'ID fourni ( en.wikipedia.org/wiki/Process_group ). Il est déroutant que cela ne soit pas mentionné dans man 1 kill
ou man bash
, et pourrait être considéré comme un bogue dans la documentation.
Mise à jour: https://stackoverflow.com/a/53714583/302079 améliore cela en ajoutant l'état de sortie et une fonction de nettoyage.
trap "exit" INT TERM
trap "kill 0" EXIT
Pourquoi convertir INT
et TERM
quitter? Parce que les deux devraient déclencher le kill 0
sans entrer dans une boucle infinie.
Pourquoi déclenchement kill 0
sur EXIT
? Parce que les sorties de script normales devraient se déclencherkill 0
.
Pourquoi kill 0
? Parce que les sous-coquilles imbriquées doivent également être tuées. Cela supprimera tout l'arbre de processus .
kill 0
signifie / fait exactement ?
piège 'kill $ (jobs -p)' EXIT
Je n'apporterais que des modifications mineures à la réponse de Johannes et j'utiliserais jobs -pr pour limiter le kill aux processus en cours et ajouter quelques signaux supplémentaires à la liste:
trap 'kill $(jobs -pr)' SIGINT SIGTERM EXIT
La trap 'kill 0' SIGINT SIGTERM EXIT
solution décrite dans la réponse de @ tokland est vraiment sympa, mais le dernier Bash plante avec une faute de segmentation lors de son utilisation. C'est parce que Bash, à partir de la version 4.3, permet la récursivité des pièges, qui devient infinie dans ce cas:
SIGINT
ou SIGTERM
ou EXIT
;kill 0
, qui est envoyé SIGTERM
à tous les processus du groupe, y compris le shell lui-même;Cela peut être contourné en désenregistrant manuellement le piège:
trap 'trap - SIGTERM && kill 0' SIGINT SIGTERM EXIT
La manière la plus sophistiquée, qui permet d'imprimer le signal reçu et évite les messages "Terminé:":
#!/usr/bin/env bash
trap_with_arg() { # from https://stackoverflow.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 : ajout d'un exemple minimal; stop
fonction améliorée pour éviter le piégeage des signaux inutiles et pour masquer les messages "Terminé:" de la sortie. Merci Trevor Boyd Smith pour les suggestions!
stop()
vous fournir le premier argument que le numéro de signal , mais alors vous coder en dur les signaux qui sont désinscrits. plutôt que de coder en dur les signaux en cours de désenregistrement, vous pouvez utiliser le premier argument pour vous désenregistrer dans la stop()
fonction (cela pourrait potentiellement arrêter d'autres signaux récursifs (autres que les 3 codés en dur)).
SIGINT
, mais kill 0
envoie SIGTERM
, qui sera à nouveau piégé. Cela ne produira cependant pas de récursion infinie, car SIGTERM
il sera piégé lors du deuxième stop
appel.
trap - $1 && kill -s $1 0
devrait probablement fonctionner mieux. Je vais tester et mettre à jour cette réponse. Merci pour la belle idée! :)
trap - $1 && kill -s $1 0
ça ne marchera pas aussi, car nous ne pouvons pas tuer avec EXIT
. Mais il suffit vraiment de dé-piéger TERM
, car kill
envoie ce signal par défaut.
EXIT
, le trap
gestionnaire de signaux n'est toujours exécuté qu'une seule fois.
Pour être sûr, je trouve préférable de définir une fonction de nettoyage et de l'appeler depuis trap:
cleanup() {
local pids=$(jobs -pr)
[ -n "$pids" ] && kill $pids
}
trap "cleanup" INT QUIT TERM EXIT [...]
ou en évitant complètement la fonction:
trap '[ -n "$(jobs -pr)" ] && kill $(jobs -pr)' INT QUIT TERM EXIT [...]
Pourquoi? Parce qu'en utilisant simplement trap 'kill $(jobs -pr)' [...]
un, on suppose qu'il y aura des travaux en arrière-plan en cours d'exécution lorsque la condition d'interruption est signalée. Lorsqu'il n'y a pas de tâches, le message suivant (ou similaire) s'affiche:
kill: usage: kill [-s sigspec | -n signum | -sigspec] pid | jobspec ... or kill -l [sigspec]
parce que jobs -pr
c'est vide - je me suis retrouvé dans ce «piège» (jeu de mots voulu).
[ -n "$(jobs -pr)" ]
ne fonctionne pas sur mon bash. J'utilise GNU bash, version 4.2.46 (2) -release (x86_64-redhat-linux-gnu). Le message "kill: usage" apparaît toujours.
jobs -pr
ne renvoie pas les PID des enfants des processus d'arrière-plan. Il ne détruit pas tout l'arbre du processus, il ne fait que couper les racines.
Une belle version qui fonctionne sous Linux, BSD et MacOS X. Essaie d'abord d'envoyer SIGTERM, et si cela ne réussit pas, tue le processus après 10 secondes.
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
Veuillez noter que les emplois n'incluent pas les processus des petits enfants.
function cleanup_func {
sleep 0.5
echo cleanup
}
trap "exit \$exit_code" INT TERM
trap "exit_code=\$?; cleanup_func; kill 0" EXIT
# exit 1
# exit 0
Comme https://stackoverflow.com/a/22644006/10082476 , mais avec un code de sortie ajouté
exit_code
vient le INT TERM
piège?
jobs -p ne fonctionne pas dans tous les shells s'il est appelé dans un sous-shell, sauf si sa sortie est redirigée dans un fichier mais pas dans un pipe. (Je suppose qu'il était initialement destiné à un usage interactif uniquement.)
Qu'en est-il des éléments suivants:
trap 'while kill %% 2>/dev/null; do jobs > /dev/null; done' INT TERM EXIT [...]
L'appel à "jobs" est nécessaire avec le shell de tableau de bord de Debian, qui ne met pas à jour le job actuel ("%%") s'il est manquant.
trap 'echo in trap; set -x; trap - TERM EXIT; while kill %% 2>/dev/null; do jobs > /dev/null; done; set +x' INT TERM EXIT; sleep 100 & while true; do printf .; sleep 1; done
Si vous l'exécutez dans Bash (5.0.3) et essayez de terminer, il semble y avoir une boucle infinie. Cependant, si vous le résiliez à nouveau, cela fonctionne. Même par Dash (0.5.10.2-6), vous devez y mettre fin deux fois.
J'ai fait une adaptation de la réponse de @ tokland combinée avec les connaissances de http://veithen.github.io/2014/11/16/sigterm-propagation.html quand j'ai remarqué que trap
cela ne se déclenche pas si j'exécute un processus de premier plan (sans arrière-plan avec &
):
#!/bin/bash
# killable-shell.sh: Kills itself and all children (the whole process group) when killed.
# Adapted from http://stackoverflow.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
Exemple de fonctionnement:
$ 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
Juste pour la diversité, je publierai une variation de https://stackoverflow.com/a/2173421/102484 , car cette solution conduit au message "Terminé" dans mon environnement:
trap 'test -z "$intrap" && export intrap=1 && kill -- -$$' SIGINT SIGTERM EXIT