Je cherche un moyen de réparer les dégâts lorsque mon script de niveau supérieur se termine.
Surtout si je veux utiliser set -e
, je souhaite que le processus d'arrière-plan meure à la fin du script.
trap
peut être utilisé pour nettoyer les dégâts. Il peut fournir une liste de choses exécutées quand un signal spécifique arrive:
trap "echo hello" SIGINT
mais peut également être utilisé pour exécuter quelque chose si le shell se ferme:
trap "killall background" EXIT
C'est une commande intégrée, donc help trap
vous donnera des informations (fonctionne avec bash). Si vous souhaitez uniquement supprimer les tâches en arrière-plan, vous pouvez le faire.
trap 'kill $(jobs -p)' EXIT
Veillez à utiliser un seul '
, pour empêcher le shell de remplacer immédiatement la $()
.
Mise à jour: https://stackoverflow.com/a/53714583/302079 améliore ceci en ajoutant un statut de sortie et une fonction de nettoyage.
trap "exit" INT TERM
trap "kill 0" EXIT
Pourquoi convertir INT
et TERM
en sortie? Parce que les deux devraient déclencher le kill 0
sans entrer dans une boucle infinie.
Pourquoi déclencher kill 0
sur EXIT
? Parce que les exits de script normaux doivent aussi déclencher kill 0
.
Pourquoi kill 0
? Parce que les sous-shell imbriqués doivent également être tués. Cela enlèvera tout l'arbre du processus .
piège 'kill $ (jobs -p)' EXIT
Je n’apporterais que des modifications mineures à la réponse de Johannes et utiliser 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 solution trap 'kill 0' SIGINT SIGTERM EXIT
décrite dans La réponse de @ tokland est vraiment agréable, mais le dernier Bash se bloque avec un défaut de segmentation lors de son utilisation. En effet, Bash, à partir du v. 4.3, autorise la récursion des interruptions, qui devient infinie dans ce cas:
SIGINT
ou SIGTERM
ou EXIT
;kill 0
, qui envoie SIGTERM
à tous les processus du groupe, y compris le shell lui-même;Cela peut être contourné en annulant manuellement l'enregistrement du piège:
trap 'trap - SIGTERM && kill 0' SIGINT SIGTERM EXIT
De manière plus sophistiquée, cela permet d’imprimer le signal reçu et d’éviter les messages "Terminated:":
#!/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: exemple minimal ajouté; amélioration de la fonction stop
pour éviter le détournement des signaux inutiles et pour masquer les messages "Terminated:" de la sortie. Merci Trevor Boyd Smith pour les suggestions!
Par mesure de sécurité, il est 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? En utilisant simplement trap 'kill $(jobs -pr)' [...]
, on suppose que y aura des tâches en arrière-plan en cours d'exécution lorsque la condition d'interruption sera signalée. Lorsqu'il n'y a pas de travail, le message suivant (ou un message similaire) apparaît:
kill: usage: kill [-s sigspec | -n signum | -sigspec] pid | jobspec ... or kill -l [sigspec]
parce que jobs -pr
est vide - je me suis retrouvé dans ce «piège» (jeu de mots).
Une version de Nice qui fonctionne sous Linux, BSD et MacOS X. Commence par essayer d’envoyer SIGTERM, et si elle échoue, 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 de 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
J'aime https://stackoverflow.com/a/22644006/10082476 , mais avec un code de sortie ajouté
Une autre option consiste à définir le script en tant que responsable du groupe de processus et à intercepter un killpg sur votre groupe de processus à la sortie.
Alors script le chargement du script. Exécutez une commande killall
(ou tout ce qui est disponible sur votre système d'exploitation) qui s'exécute dès que le script est terminé.
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 canal. (Je suppose qu'il a été conçu à l'origine pour une utilisation interactive uniquement.)
Qu'en est-il de ce qui suit:
trap 'while kill %% 2>/dev/null; do jobs > /dev/null; done' INT TERM EXIT [...]
L'appel à "travaux" est nécessaire avec le dash Shell de Debian, qui ne parvient pas à mettre à jour le travail actuel ("%%") s'il est manquant.
J'ai fait une adaptation de la réponse de @ tokland associée aux connaissances de http://veithen.github.io/2014/11/16/sigterm-propagation.html quand j'ai remarqué que trap
ne se déclenche pas si m en exécutant un processus en avant-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 travail:
$ 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 posterai une variation de https://stackoverflow.com/a/2173421/102484 , car cette solution entraîne le message "Terminated" dans mon environnement:
trap 'test -z "$intrap" && export intrap=1 && kill -- -$$' SIGINT SIGTERM EXIT