web-dev-qa-db-fra.com

Comment puis-je supprimer les processus/travaux en arrière-plan lorsque mon script Shell se termine?

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.

153
elmarco

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 $()

158

Cela fonctionne pour moi (amélioré grâce aux commentateurs):

trap "trap - SIGTERM && kill -- -$$" SIGINT SIGTERM EXIT
  • kill -- -$$ envoie un SIGTERM à tout le groupe de processus, tuant ainsi les descendants.

  • Spécifier le signal EXIT est utile lorsque vous utilisez set -e (plus de détails ici ).

135
tokland

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 .

97
korkman

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
20
raytraced

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:

  1. Le processus shell reçoit SIGINT ou SIGTERM ou EXIT;
  2. le signal est piégé, en exécutant kill 0, qui envoie SIGTERM à tous les processus du groupe, y compris le shell lui-même;
  3. allez à 1 :)

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!

11
skozin

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).

7
tdaitx

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.

2
Orsiris de Jong
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é

1
Delaware

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.

1
orip

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é.

0
Oli

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.

0
michaeljt

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
0
nh2

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
0
noonex