Quelle est la meilleure façon d'envoyer un signal à tous les membres d'un groupe de processus?


422

Je veux tuer tout un arbre de processus. Quelle est la meilleure façon de procéder en utilisant des langages de script courants? Je cherche une solution simple.


3
Les zombies devraient disparaître lorsque la faucheuse système fonctionne. J'avoue que j'ai vu des systèmes où s'attardent des zombies, mais c'est atypique.
Brian Knoblauch

7
Parfois, ces zombies persistants sont responsables d'une activité effrayante.
User1

Utilisez l'une des commandes chronosou herodes.
Michael Le Barbier Grünewald

8
kill $ (pstree <PID> -p -a -l | cut -d, -f2 | cut -d '' -f1)
vrdhn

@ MichaelLeBarbierGrünewald Pourriez-vous s'il vous plaît lier à ces programmes?
mouche en polystyrène

Réponses:


305

Vous ne dites pas si l'arbre que vous voulez tuer est un seul groupe de processus. (C'est souvent le cas si l'arborescence est le résultat d'un fork d'un démarrage de serveur ou d'une ligne de commande shell.) Vous pouvez découvrir des groupes de processus en utilisant GNU ps comme suit:

 ps x -o  "%p %r %y %x %c "

Si c'est un groupe de processus que vous voulez tuer, utilisez simplement la kill(1)commande mais au lieu de lui donner un numéro de processus, donnez-lui la négation du numéro de groupe. Par exemple, pour tuer tous les processus du groupe 5112, utilisez kill -TERM -- -5112.


3
kill -74313 -bash: kill: 74313: spécification de signal invalide Si j'ajoute le kill -15 -GPID cela a fonctionné parfaitement.
Adam Peck,

51
Comme d'habitude avec presque toutes les commandes, si vous voulez qu'un argument normal qui commence par un - ne soit pas interprété comme un commutateur, faites
ysth

9
pgreppeut offrir un moyen plus simple de trouver l'ID de groupe de processus. Par exemple, pour tuer le groupe de processus de my-script.sh, exécutez kill -TERM -$(pgrep -o my-script.sh).
Josh Kelley

9
Mieux vaut regarder stackoverflow.com/questions/392022/… c'est de loin une solution plus élégante et si vous avez besoin de lister les pids des enfants alors utilisez:ps -o pid --no-headers --ppid $PARENT_PID
Szymon Jeż

4
Et si vous modifiez légèrement le format et triez, vous pouvez voir tous les processus bien regroupés et en commençant par (potentiellement) le parent du groupe dans chaque groupe:ps x -o "%r %p %y %x %c" | sort -nk1,2
haridsv

199

Tuez tous les processus appartenant à la même arborescence de processus à l'aide de l' ID de groupe de processus ( PGID)

  • kill -- -$PGID     Utiliser le signal par défaut ( TERM= 15)
  • kill -9 -$PGID     Utilisez le signal KILL(9)

Vous pouvez récupérer le à PGIDpartir de n'importe quel ID de processus ( PID) de la même arborescence de processus

  • kill -- -$(ps -o pgid= $PID | grep -o '[0-9]*')   (signal TERM)
  • kill -9 -$(ps -o pgid= $PID | grep -o '[0-9]*')   (signal KILL)

Un merci spécial à tanager et Speakus pour leurs contributions sur $PIDles espaces restants et la compatibilité OSX.

Explication

  • kill -9 -"$PGID"=> Envoyer le signal 9 ( KILL) à tous les enfants et petits-enfants ...
  • PGID=$(ps opgid= "$PID")=> Récupérer l' ID de groupe de processus à partir de n'importe quel ID de processus de l'arborescence, pas seulement l' ID de parent de processus . Une variation de ps opgid= $PIDest ps -o pgid --no-headers $PIDpgidpeut être remplacée par pgrp.
    Mais:
    • psinserts menant espaces lorsque PIDest inférieur à cinq chiffres et aligné à droite comme remarqué par tanager . Vous pouvez utiliser:
      PGID=$(ps opgid= "$PID" | tr -d ' ')
    • psdepuis OSX imprime toujours l'en-tête, donc Speakus propose:
      PGID="$( ps -o pgid "$PID" | grep [0-9] | tr -d ' ' )"
  • grep -o [0-9]* imprime uniquement les chiffres successifs (n'imprime pas les espaces ni les en-têtes alphabétiques).

Autres lignes de commande

PGID=$(ps -o pgid= $PID | grep -o [0-9]*)
kill -TERM -"$PGID"  # kill -15
kill -INT  -"$PGID"  # correspond to [CRTL+C] from keyboard
kill -QUIT -"$PGID"  # correspond to [CRTL+\] from keyboard
kill -CONT -"$PGID"  # restart a stopped process (above signals do not kill it)
sleep 2              # wait terminate process (more time if required)
kill -KILL -"$PGID"  # kill -9 if it does not intercept signals (or buggy)

Limitation

  • Comme l'ont remarqué davide et Hubert Kario , lorsqu'il killest invoqué par un processus appartenant au même arbre, il killrisque de se tuer avant de mettre fin à la destruction de l'arbre entier.
  • Par conséquent, assurez-vous d'exécuter la commande à l'aide d'un processus ayant un ID de groupe de processus différent .

Longue histoire

> cat run-many-processes.sh
#!/bin/sh
echo "ProcessID=$$ begins ($0)"
./child.sh background &
./child.sh foreground
echo "ProcessID=$$ ends ($0)"

> cat child.sh
#!/bin/sh
echo "ProcessID=$$ begins ($0)"
./grandchild.sh background &
./grandchild.sh foreground
echo "ProcessID=$$ ends ($0)"

> cat grandchild.sh
#!/bin/sh
echo "ProcessID=$$ begins ($0)"
sleep 9999
echo "ProcessID=$$ ends ($0)"

Exécutez l'arborescence des processus en arrière-plan à l'aide de '&'

> ./run-many-processes.sh &    
ProcessID=28957 begins (./run-many-processes.sh)
ProcessID=28959 begins (./child.sh)
ProcessID=28958 begins (./child.sh)
ProcessID=28960 begins (./grandchild.sh)
ProcessID=28961 begins (./grandchild.sh)
ProcessID=28962 begins (./grandchild.sh)
ProcessID=28963 begins (./grandchild.sh)

> PID=$!                    # get the Parent Process ID
> PGID=$(ps opgid= "$PID")  # get the Process Group ID

> ps fj
 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
28348 28349 28349 28349 pts/3    28969 Ss   33021   0:00 -bash
28349 28957 28957 28349 pts/3    28969 S    33021   0:00  \_ /bin/sh ./run-many-processes.sh
28957 28958 28957 28349 pts/3    28969 S    33021   0:00  |   \_ /bin/sh ./child.sh background
28958 28961 28957 28349 pts/3    28969 S    33021   0:00  |   |   \_ /bin/sh ./grandchild.sh background
28961 28965 28957 28349 pts/3    28969 S    33021   0:00  |   |   |   \_ sleep 9999
28958 28963 28957 28349 pts/3    28969 S    33021   0:00  |   |   \_ /bin/sh ./grandchild.sh foreground
28963 28967 28957 28349 pts/3    28969 S    33021   0:00  |   |       \_ sleep 9999
28957 28959 28957 28349 pts/3    28969 S    33021   0:00  |   \_ /bin/sh ./child.sh foreground
28959 28960 28957 28349 pts/3    28969 S    33021   0:00  |       \_ /bin/sh ./grandchild.sh background
28960 28964 28957 28349 pts/3    28969 S    33021   0:00  |       |   \_ sleep 9999
28959 28962 28957 28349 pts/3    28969 S    33021   0:00  |       \_ /bin/sh ./grandchild.sh foreground
28962 28966 28957 28349 pts/3    28969 S    33021   0:00  |           \_ sleep 9999
28349 28969 28969 28349 pts/3    28969 R+   33021   0:00  \_ ps fj

La commande pkill -P $PIDne tue pas le petit-enfant:

> pkill -P "$PID"
./run-many-processes.sh: line 4: 28958 Terminated              ./child.sh background
./run-many-processes.sh: line 4: 28959 Terminated              ./child.sh foreground
ProcessID=28957 ends (./run-many-processes.sh)
[1]+  Done                    ./run-many-processes.sh

> ps fj
 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
28348 28349 28349 28349 pts/3    28987 Ss   33021   0:00 -bash
28349 28987 28987 28349 pts/3    28987 R+   33021   0:00  \_ ps fj
    1 28963 28957 28349 pts/3    28987 S    33021   0:00 /bin/sh ./grandchild.sh foreground
28963 28967 28957 28349 pts/3    28987 S    33021   0:00  \_ sleep 9999
    1 28962 28957 28349 pts/3    28987 S    33021   0:00 /bin/sh ./grandchild.sh foreground
28962 28966 28957 28349 pts/3    28987 S    33021   0:00  \_ sleep 9999
    1 28961 28957 28349 pts/3    28987 S    33021   0:00 /bin/sh ./grandchild.sh background
28961 28965 28957 28349 pts/3    28987 S    33021   0:00  \_ sleep 9999
    1 28960 28957 28349 pts/3    28987 S    33021   0:00 /bin/sh ./grandchild.sh background
28960 28964 28957 28349 pts/3    28987 S    33021   0:00  \_ sleep 9999

La commande kill -- -$PGIDtue tous les processus, y compris le petit-enfant.

> kill --    -"$PGID"  # default signal is TERM (kill -15)
> kill -CONT -"$PGID"  # awake stopped processes
> kill -KILL -"$PGID"  # kill -9 to be sure

> ps fj
 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
28348 28349 28349 28349 pts/3    29039 Ss   33021   0:00 -bash
28349 29039 29039 28349 pts/3    29039 R+   33021   0:00  \_ ps fj

Conclusion

Je remarque dans cet exemple PIDet suis PGIDégal ( 28957).
C'est pourquoi je pensais à l'origine que kill -- -$PIDc'était suffisant. Mais dans le cas où le processus est fraient dans un Makefilel' ID de processus est différent de l' ID de groupe .

Je pense que kill -- -$(ps -o pgid= $PID | grep -o [0-9]*)c'est la meilleure astuce simple pour tuer un arbre de processus entier lorsqu'il est appelé à partir d'un ID de groupe différent (un autre arbre de processus).


1
Salut @davide. Bonne question. Je pense que killdevrait toujours envoyer le signal à l'arbre entier avant de recevoir son propre signal. Mais dans certaines circonstances / implémentations spécifiques, il killpeut s’envoyer le signal, être interrompu, puis recevoir son propre signal. Cependant, le risque doit être suffisamment minime et peut être ignoré dans la plupart des cas, car d'autres bogues doivent se produire avant celui-ci. Ce risque peut-il être ignoré dans votre cas? De plus, d'autres réponses ont ce bogue commun (une killpartie de l'arbre de processus est en cours de suppression). J'espère que cette aide .. Cheers;)
olibre

3
Cela ne fonctionne que si les sous-commandes elles-mêmes ne deviennent pas des chefs de groupe. Même des outils aussi simples comme mança. D'un autre côté, si vous voulez tuer le processus gandchild du processus enfant, kill -- -$pidcela ne fonctionnera pas. Ce n'est donc pas une solution générique.
Hubert Kario

1
L'exemple est "l'enfant" essayant de tuer ses enfants (donc les petits-enfants de la commande initiée par l'utilisateur). IOW, essayez de tuer la hiérarchie du processus d'arrière-plan dans child.sh.
Hubert Kario

1
> kill -QUIT - "$ PGID" # même signal que [CRTL + C] du clavier ---- QUIT devrait être remplacé par INT pour être vrai
Maxim Kholyavkin

1
pour l'option OSX - no-headers n'est pas pris en charge, le code doit donc être mis à jour vers: PGID = "$ (ps -o pgid" $ PID "| grep [0-9] | tr -d '')"
Maxim Kholyavkin

166
pkill -TERM -P 27888

Cela tuera tous les processus qui ont l'ID de processus parent 27888.

Ou plus robuste:

CPIDS=$(pgrep -P 27888); (sleep 33 && kill -KILL $CPIDS &); kill -TERM $CPIDS

qui doit tuer 33 secondes plus tard et demander poliment aux processus de se terminer.

Voir cette réponse pour mettre fin à tous les descendants.


19
Dans mon test rapide, pgrep n'a signalé que les enfants immédiats, donc cela peut ne pas tuer toute la hiérarchie.
haridsv

10
Je suis d'accord avec @haridsv: pkill -Penvoie le signal à l'enfant uniquement => le petit-enfant ne reçoit pas le signal => J'ai donc écrit une autre réponse pour l'expliquer.
Santé

1
À partir d'un script bash, pour tuer vos propres enfants, utilisez pkill -TERM -P ${$}.
François Beausoleil

3
@Onlyjob, n'est-il pas dangereux de dormir puis de tuer? Les ID de processus peuvent avoir été réutilisés par le système d'exploitation entre-temps: vous pourriez tuer des processus qui ne sont plus vos enfants. Je soupçonne que l'appel à pkill devrait être refait pour éviter cela.
François Beausoleil

2
@Onlyjob FYI, je suis capable de générer 32768 processus, simple thread, avec un accès E / S léger en moins de 19 secondes sur ma machine:$ time for i in {1..32768}; do ( echo $BASHPID >> pids ); done real 0m18.860s
cychoi

102

Pour tuer une arborescence de processus récursivement, utilisez killtree ():

#!/bin/bash

killtree() {
    local _pid=$1
    local _sig=${2:--TERM}
    kill -stop ${_pid} # needed to stop quickly forking parent from producing children between child killing and parent killing
    for _child in $(ps -o pid --no-headers --ppid ${_pid}); do
        killtree ${_child} ${_sig}
    done
    kill -${_sig} ${_pid}
}

if [ $# -eq 0 -o $# -gt 2 ]; then
    echo "Usage: $(basename $0) <pid> [signal]"
    exit 1
fi

killtree $@

3
Les --arguments pour psne pas fonctionner sous OS X. Pour le faire fonctionner, remplacez la pscommande par: ps ax -o "pid= ppid=" | grep -E "${_regex}" | sed -E "s/${_regex}/\1/g_regexest défini avant la forboucle:local _regex="[ ]*([0-9]+)[ ]+${_pid}"
artur

5
Les processus arrêtés ne sont pas tués avec SIGTERM. Voir ma réponse
x-yuri

1
-1 utilise #! / Bin / bash au lieu de #! / Usr / bin / env bash (ou mieux encore les constructions POSIX et / bin / sh)
Good Person

Serait-il suffisant d'envoyer un SIGKILL à la place de SIGTERM pour garantir que cela killtree()fonctionne de manière fiable?
davide

3
si psne prend pas en charge --ppid, on peut utiliser à la pgrep -P ${_pid}place
Hubert Kario

16

La commande rkill du package pslist envoie le signal donné (ouSIGTERMpar défaut) au processus spécifié et à tous ses descendants:

rkill [-SIG] pid/name...

11

La réponse de Brad est ce que je recommanderais aussi, sauf que vous pouvez supprimer awkcomplètement si vous utilisez l' --ppidoption pour ps.

for child in $(ps -o pid -ax --ppid $PPID) do ....... done

Cela ne fonctionne pas pour moi sauf si je supprime -ax, pour une raison quelconque (Centos5). Sinon c'est super!
2010 à 9h03

9

si vous savez passer le pid du processus parent, voici un script shell qui devrait fonctionner:

for child in $(ps -o pid,ppid -ax | \
   awk "{ if ( \$2 == $pid ) { print \$1 }}")
do
  echo "Killing child process $child because ppid = $pid"
  kill $child
done

Certaines versions de ps émettront un avertissement si vous utilisez "-ax" au lieu de "ax". Ainsi: pour l'enfant dans $ (ps -o pid, ppid ax | \ awk "{if (\ $ 2 == $ pid) {print \ $ 1}}")
Cory R. King

9

J'utilise une version légèrement modifiée d'une méthode décrite ici: https://stackoverflow.com/a/5311362/563175

Donc ça ressemble à ça:

kill `pstree -p 24901 | sed 's/(/\n(/g' | grep '(' | sed 's/(\(.*\)).*/\1/' | tr "\n" " "`

où 24901 est le PID du parent.

Il a l'air assez moche mais fait son travail parfaitement.


1
Simplifier avec grep, au lieu de sed ...pstree -p 24901 | grep -oP '(?<=\()[0-9]+(?=\))'
anishsane

2
vous devez ajouter -là pstree, donc les longues lignes ne sont pas tronquées; il peut être rendu plus simple à lire également avec kill `pstree -l -p 24901 |grep "([[:digit:]]*)" -o |tr -d '()'`(pas besoin de convertir \nen espace car cela fonctionnera bien), merci!
Aquarius Power

9

Version modifiée de la réponse de zhigang:

#!/usr/bin/env bash
set -eu

killtree() {
    local pid
    for pid; do
        kill -stop $pid
        local cpid
        for cpid in $(pgrep -P $pid); do
            killtree $cpid
        done
        kill $pid
        kill -cont $pid
        wait $pid 2>/dev/null || true
   done
}

cpids() {
    local pid=$1 options=${2:-} space=${3:-}
    local cpid
    for cpid in $(pgrep -P $pid); do
        echo "$space$cpid"
        if [[ "${options/a/}" != "$options" ]]; then
            cpids $cpid "$options" "$space  "
        fi
    done
}

while true; do sleep 1; done &
cpid=$!
for i in $(seq 1 2); do
    cpids $$ a
    sleep 1
done
killtree $cpid
echo ---
cpids $$ a

vous ne pouvez le faire wait $pidque sur les processus que vous avez démarrés, pas sur tous les processus, donc ce n'est pas une solution générique
Hubert Kario

@Hubert Kario Dans ce cas, l'attente se terminera avec un état différent de zéro et continuera d'exécuter le script. Ai-je tort? Mais waitsupprimera le Terminatedmessage s'il s'agit d'un enfant.
x-yuri

9

Je ne peux pas commenter (pas assez de réputation), donc je suis obligé d'ajouter une nouvelle réponse , même si ce n'est pas vraiment une réponse.

Il y a un léger problème avec la réponse par ailleurs très agréable et approfondie donnée par @olibre le 28 février. La sortie de ps opgid= $PIDcontiendra des espaces de tête pour un PID inférieur à cinq chiffres car psjustifie la colonne (alignez les chiffres). Dans toute la ligne de commande, cela se traduit par un signe négatif, suivi par des espaces, suivi du groupe PID. La solution simple consiste à diriger psvers trpour supprimer les espaces:

kill -- -$( ps opgid= $PID | tr -d ' ' )

Merci tanneur. Je vais corriger ma réponse;) Cheers
olibre

8

Pour compléter la réponse de Norman Ramsey, il peut être utile de regarder setsid si vous souhaitez créer un groupe de processus.
http://pubs.opengroup.org/onlinepubs/009695399/functions/setsid.html

La fonction setsid () doit créer une nouvelle session, si le processus appelant n'est pas un leader de groupe de processus. A son retour, le processus appelant sera le chef de session de cette nouvelle session, sera le chef de groupe de processus d'un nouveau groupe de processus et n'aura aucun terminal de contrôle. L'ID de groupe de processus du processus appelant doit être égal à l'ID de processus du processus appelant. Le processus appelant doit être le seul processus du nouveau groupe de processus et le seul processus de la nouvelle session.

Ce que je suppose que vous pouvez créer un groupe à partir du processus de démarrage. Je l'ai utilisé en php afin de pouvoir tuer tout un arbre de processus après l'avoir démarré.

Cela peut être une mauvaise idée. Je serais intéressé par des commentaires.


En fait, c'est une excellente idée et fonctionne très bien. Je l'utilise dans les cas où je peux mettre des processus dans le même groupe de processus (ou ils sont déjà dans le même groupe).
2013

7

Inspiré par le commentaire d' Ysth

kill -- -PGID

au lieu de lui donner un numéro de processus, donnez-lui la négation du numéro de groupe. Comme d'habitude avec presque toutes les commandes, si vous voulez qu'un argument normal commençant par un -ne soit pas interprété comme un commutateur, faites-le précéder de--


Oups, je viens de réaliser que j'ai donné la même réponse que vous => +1. Mais en plus j'explique comment simplement PGIDpartir PID. Qu'est-ce que tu penses? Vive
olibre

5

La fonction shell suivante est similaire à la plupart des autres réponses, mais elle fonctionne à la fois sur Linux et BSD (OS X, etc.) sans dépendances externes comme pgrep:

killtree() {
    local parent=$1 child
    for child in $(ps -o ppid= -o pid= | awk "\$1==$parent {print \$2}"); do
        killtree $child
    done
    kill $parent
}

Je pense que vous avez un mot supplémentaire "enfant" à la fin de la 2ème ligne.
mato

@mato - Ce n'est pas extra. Il limite la portée de $childcette fonction afin de ne pas perturber les autres variables (non locales) du même nom et de garantir que la valeur de la variable locale est nettoyée après la fin de la fonction.
Adam Katz

Oh, je vois maintenant, je pensais que cela faisait partie de la mission. Je suppose que je devrais relire (plus lentement) avant d'écrire un commentaire. :) Merci.
mato

Au fait, ne serait-il pas préférable de créer une liste d'enfants et de commencer à tuer par le haut (parent)? .. Ainsi, nous pourrions éviter une situation où un parent recrée un enfant ou continue d'exécuter le code suivant, ce qui pourrait potentiellement modifier le comportement souhaité.
mato

5

C'est super facile de le faire avec python en utilisant psutil . Installez simplement psutil avec pip et vous disposez d'une suite complète d'outils de manipulation de processus:

def killChildren(pid):
    parent = psutil.Process(pid)
    for child in parent.get_children(True):
        if child.is_running():
            child.terminate()

5

Sur la base de la réponse de zhigang, cela évite l'auto-destruction:

init_killtree() {
    local pid=$1 child

    for child in $(pgrep -P $pid); do
        init_killtree $child
    done
    [ $pid -ne $$ ] && kill -kill $pid
}

4

Si vous voulez tuer un processus par son nom:

killall -9 -g someprocessname

ou

pgrep someprocessname | xargs pkill -9 -g

3

Ceci est ma version de tuer tous les processus enfants en utilisant le script bash. Il n'utilise pas de récursivité et dépend de la commande pgrep.

Utilisation

killtree.sh PID SIGNAL

Contenu de killtrees.sh

#!/bin/bash
PID=$1
if [ -z $PID ];
then
    echo "No pid specified"
fi

PPLIST=$PID
CHILD_LIST=`pgrep -P $PPLIST -d,`

while [ ! -z "$CHILD_LIST" ]
do
    PPLIST="$PPLIST,$CHILD_LIST"
    CHILD_LIST=`pgrep -P $CHILD_LIST -d,`
done

SIGNAL=$2

if [ -z $SIGNAL ]
then
    SIGNAL="TERM"
fi
#do substring from comma to space
kill -$SIGNAL ${PPLIST//,/ }

1
Cela échouera si de nouveaux processus sont créés après la création de la liste enfant.
ceving

3

Voici une variante de la réponse de @ zhigang qui se passe d'AWK, en se basant uniquement sur les possibilités d'analyse native de Bash:

function killtree {
  kill -STOP "$1"
  ps -e -o pid= -o ppid= | while read -r pid ppid
                           do
                             [[ $ppid = $1 ]] || continue
                             killtree "$pid"  || true # Skip over failures
                           done
  kill -CONT "$1"          
  kill -TERM "$1"
}

Il semble bien fonctionner à la fois sur Mac et Linux. Dans les situations où vous ne pouvez pas compter sur la capacité de gérer des groupes de processus - comme lors de l'écriture de scripts pour tester un logiciel qui doit être construit dans plusieurs environnements - cette technique d'arborescence est certainement utile.


1

Merci pour votre sagesse, les amis. Mon script laissait certains processus enfants à la sortie et la pointe de négation a rendu les choses plus faciles. J'ai écrit cette fonction pour être utilisée dans d'autres scripts si nécessaire:

# kill my group's subprocesses:          killGroup
# kill also myself:                      killGroup -x
# kill another group's subprocesses:     killGroup N  
# kill that group all:                   killGroup -x N
# N: PID of the main process (= process group ID).

function killGroup () {
    local prid mainpid
    case $1 in
        -x) [ -n "$2" ] && kill -9 -$2 || kill -9 -$$ ;;
        "") mainpid=$$ ;;
         *) mainpid=$1 ;;
    esac
    prid=$(ps ax -o pid,pgid | grep $mainpid)
    prid=${prid//$mainpid/}
    kill -9 $prid 2>/dev/null
    return
}

À votre santé.


1

si vous avez pstree et perl sur votre système, vous pouvez essayer ceci:

perl -e 'kill 9, (`pstree -p PID` =~ m/\((\d+)\)/sg)'

1

Il vaut probablement mieux tuer le parent avant les enfants; sinon, le parent peut probablement engendrer de nouveaux enfants avant qu'il ne soit tué lui-même. Ceux-ci survivront au massacre.

Ma version de ps est différente de celle ci-dessus; peut-être trop vieux, donc l'étrange avidité ...

Utiliser un script shell au lieu d'une fonction shell présente de nombreux avantages ...

Cependant, c'est essentiellement l'idée de zhigangs


#!/bin/bash
if test $# -lt 1 ; then
    echo >&2 "usage: kiltree pid (sig)"
fi ;

_pid=$1
_sig=${2:-TERM}
_children=$(ps j | grep "^[ ]*${_pid} " | cut -c 7-11) ;
echo >&2 kill -${_sig} ${_pid}
kill -${_sig} ${_pid}
for _child in ${_children}; do
    killtree ${_child} ${_sig}
done

notez que le script @zhighang SIGSTOPs le processus parent et fournit le signal au processus arrêté, donc cela ne devrait pas (AFAIK) provoquer une condition de concurrence entre la création de processus enfants et la livraison du signal. Votre version a cependant une course entre l'obtention de la liste des enfants et la transmission du signal aux parents.
Hubert Kario

1

Ce qui suit a été testé sur FreeBSD, Linux et MacOS X et ne dépend que de pgrep et kill (les versions ps -o ne fonctionnent pas sous BSD). Le premier argument est le parent pid dont les enfants doivent être supprimés. le deuxième argument est un booléen pour déterminer si le pid parent doit également être terminé.

KillChilds() {
        local pid="${1}"
        local self="${2:-false}"

        if children="$(pgrep -P "$pid")"; then
                for child in $children; do
                        KillChilds "$child" true
                done
        fi

        if [ "$self" == true ]; then
                kill -s SIGTERM "$pid" || (sleep 10 && kill -9 "$pid" &)
        fi
}

KillChilds $$ > /dev/null 2>&1

Cela enverra SIGTERM à tout processus enfant / petit-enfant dans un script shell et si SIGTERM ne réussit pas, il attendra 10 secondes, puis enverra kill.


Réponse antérieure:

Ce qui suit fonctionne également mais tuera le shell lui-même sur BSD.

KillSubTree() {
    local parent="${1}"
    for child in $(ps -o pid=$parent); do
            if [ $$ -ne $child ]; then (kill -s SIGTERM $child || (sleep 10 && kill -9 $child & )) > /dev/null 2>&1 ; fi
    done
}
# Example lanch from within script
KillSubTree $$ > /dev/null 2>&1

1

Je développe encore la solution de zhigang, xyuri et solidsneck:

 #!/bin/bash

if test $# -lt 1 ; then
    echo >&2 "usage: kiltree pid (sig)"
    exit 1 ;
  fi ;

_pid=$1
_sig=${2:-TERM}

# echo >&2 "killtree($_pid) mypid = $$"
# ps axwwf | grep -6 "^[ ]*$_pid " >&2 ;

function _killtree () {
    local _children
    local _child
    local _success

    if test $1 -eq $2 ; then # this is killtree - don't commit suicide!
        echo >&2 "killtree can´t kill it´s own branch - some processes will survive." ; 
        return 1 ;
      fi ;
    # this avoids that children are spawned or disappear.
    kill -SIGSTOP $2 ;

    _children=$(ps -o pid --no-headers --ppid $2) ;        
    _success=0 
    for _child in ${_children}; do
        _killtree $1 ${_child} $3 ;
        _success=$(($_success+$?)) ;
      done ;

    if test $_success -eq 0 ; then
        kill -$3 $2
      fi ;
    # when a stopped process is killed, it will linger in the system until it is continued
    kill -SIGCONT $2
    test $_success -eq 0 ;
    return $?
    }

_killtree $$ $_pid $_sig

Cette version évitera de tuer son ascendance - ce qui provoque un déluge de processus enfants dans les solutions précédentes.

Les processus sont correctement arrêtés avant que la liste d'enfants ne soit déterminée, afin qu'aucun nouvel enfant ne soit créé ou disparaisse.

Après avoir été tués, les emplois suspendus doivent continuer à disparaître du système.


1

Vieille question, je sais, mais toutes les réponses semblent continuer d'appeler ps, ce que je n'ai pas aimé.

Cette solution basée sur awk ne nécessite pas de récursivité et n'appelle ps qu'une seule fois.

awk 'BEGIN {
  p=1390
  while ("ps -o ppid,pid"|getline) a[$1]=a[$1]" "$2
  o=1
  while (o==1) {
    o=0
    split(p, q, " ")
    for (i in q) if (a[q[i]]!="") {
      p=p""a[q[i]]
      o=1
      a[q[i]]=""
    }
  }
  system("kill -TERM "p)
}'

Ou sur une seule ligne:

awk 'BEGIN {p=1390;while ("ps -o ppid,pid"|getline) a[$1]=a[$1]" "$2;o=1;while (o==1) {o=0;split(p, q, " ");for (i in q) {if (a[q[i]]!="") {p=p""a[q[i]];o=1;a[q[i]]=""}}}system("kill -TERM "p)}'

Fondamentalement, l'idée est de créer un tableau (a) d'entrées parent: enfant, puis de parcourir le tableau pour trouver des enfants pour nos parents correspondants, en les ajoutant à notre liste de parents (p) au fur et à mesure.

Si vous ne voulez pas tuer le processus de niveau supérieur, alors faites

sub(/[0-9]*/, "", p)

juste avant que la ligne system () ne la supprime du kill set.

Gardez à l'esprit qu'il y a une condition de concurrence ici, mais c'est vrai (pour autant que je puisse voir) de toutes les solutions. Il fait ce dont j'avais besoin parce que le script dont j'avais besoin ne crée pas beaucoup d'enfants de courte durée.

Un exercice pour le lecteur serait d'en faire une boucle en 2 passes: après la première passe, envoyez SIGSTOP à tous les processus de la liste p, puis bouclez pour relancer ps et après la deuxième passe envoyez SIGTERM, puis SIGCONT. Si vous ne vous souciez pas des fins agréables, le second passage pourrait simplement être SIGKILL, je suppose.


0

Si vous connaissez le pid de la chose que vous voulez tuer, vous pouvez généralement aller à partir de l'ID de session et de tout dans la même session. Je revérifierais, mais je l'ai utilisé pour les scripts démarrant rsyncs dans des boucles que je veux mourir, et pas pour en démarrer une autre (à cause de la boucle) comme si je venais de tuer kills rsync.

kill $(ps -o pid= -s $(ps -o sess --no-heading --pid 21709))

Si vous ne connaissez pas le pid, vous pouvez toujours imbriquer plus

kill $(ps -o pid= -s $(ps -o sess --no-heading --pid $(pgrep rsync )))


0

Dans sh, la commande jobs listera les processus d'arrière-plan. Dans certains cas, il peut être préférable de tuer le processus le plus récent en premier, par exemple le plus ancien a créé une socket partagée. Dans ces cas, triez les PID dans l'ordre inverse. Parfois, vous voulez attendre un moment pour que les travaux écrivent quelque chose sur le disque ou des trucs comme ça avant de s'arrêter.

Et ne tuez pas si vous n'êtes pas obligé!

for SIGNAL in TERM KILL; do
  for CHILD in $(jobs -s|sort -r); do
    kill -s $SIGNAL $CHILD
    sleep $MOMENT
  done
done

0

Tuer le processus enfant dans le script shell:

Plusieurs fois, nous devons tuer les processus enfants qui sont suspendus ou bloqués pour une raison quelconque. par exemple. Problème de connexion FTP.

Il existe deux approches,

1) Pour créer un nouveau parent distinct pour chaque enfant qui surveillera et tuera le processus enfant une fois le délai écoulé.

Créez test.sh comme suit,

#!/bin/bash

declare -a CMDs=("AAA" "BBB" "CCC" "DDD")
for CMD in ${CMDs[*]}; do
    (sleep 10 & PID=$!; echo "Started $CMD => $PID"; sleep 5; echo "Killing $CMD => $PID"; kill $PID; echo "$CMD Completed.") &
done
exit;

et regarder les processus dont le nom est «test» dans un autre terminal en utilisant la commande suivante.

watch -n1 'ps x -o "%p %r %c" | grep "test" '

Le script ci-dessus créera 4 nouveaux processus enfants et leurs parents. Chaque processus enfant s'exécutera pendant 10 secondes. Mais une fois le délai de 5 secondes atteint, leurs processus parent respectifs tueront ces enfants. Ainsi, l'enfant ne pourra pas terminer l'exécution (10sec). Jouez autour de ces horaires (commutateurs 10 et 5) pour voir un autre comportement. Dans ce cas, l'enfant terminera l'exécution en 5 secondes avant d'atteindre le délai d'attente de 10 secondes.

2) Laissez le parent actuel surveiller et tuer le processus enfant une fois le délai écoulé. Cela ne créera pas de parent séparé pour surveiller chaque enfant. Vous pouvez également gérer correctement tous les processus enfants au sein du même parent.

Créez test.sh comme suit,

#!/bin/bash

declare -A CPIDs;
declare -a CMDs=("AAA" "BBB" "CCC" "DDD")

CMD_TIME=15;
for CMD in ${CMDs[*]}; do
    (echo "Started..$CMD"; sleep $CMD_TIME; echo "$CMD Done";) &
    CPIDs[$!]="$RN";
    sleep 1;
done

GPID=$(ps -o pgid= $$);
CNT_TIME_OUT=10;
CNT=0;
while (true); do
    declare -A TMP_CPIDs;

    for PID in "${!CPIDs[@]}"; do
        echo "Checking "${CPIDs[$PID]}"=>"$PID;

        if ps -p $PID > /dev/null ; then
          echo "-->"${CPIDs[$PID]}"=>"$PID" is running..";
          TMP_CPIDs[$PID]=${CPIDs[$PID]};
        else
          echo "-->"${CPIDs[$PID]}"=>"$PID" is completed.";
        fi
    done

    if [ ${#TMP_CPIDs[@]} == 0 ]; then
        echo "All commands completed.";
        break;
    else
        unset CPIDs;
        declare -A CPIDs;
        for PID in "${!TMP_CPIDs[@]}"; do
            CPIDs[$PID]=${TMP_CPIDs[$PID]};
        done
        unset TMP_CPIDs;

        if [ $CNT -gt $CNT_TIME_OUT ]; then
            echo ${CPIDs[@]}"PIDs not reponding. Timeout reached $CNT sec. killing all childern with GPID $GPID..";
            kill -- -$GPID;
        fi
    fi

    CNT=$((CNT+1));
    echo "waiting since $b secs..";
    sleep 1;
done

exit;

et regarder les processus dont le nom est «test» dans un autre terminal en utilisant la commande suivante.

watch -n1 'ps x -o "%p %r %c" | grep "test" '

Le script ci-dessus créera 4 nouveaux processus enfants. Nous stockons les pids de tous les processus enfants et les bouclons pour vérifier s'ils ont terminé leur exécution ou sont toujours en cours d'exécution. Le processus enfant sera exécuté jusqu'à l'heure CMD_TIME. Mais si le délai d'expiration de CNT_TIME_OUT est atteint, tous les enfants seront tués par le processus parent. Vous pouvez changer de timing et jouer avec le script pour voir le comportement. Un inconvénient de cette approche est qu'elle utilise l'ID de groupe pour tuer tous les arbres enfants. Mais le processus parent lui-même appartient au même groupe, il sera donc également tué.

Vous devrez peut-être attribuer un autre identifiant de groupe au processus parent si vous ne voulez pas que le parent soit tué.

Plus de détails ici,

Tuer un processus enfant dans un script shell


0

Ce script fonctionne également:

#/bin/sh while true do echo "Enter parent process id [type quit for exit]" read ppid if [ $ppid -eq "quit" -o $ppid -eq "QUIT" ];then exit 0 fi for i in `ps -ef| awk '$3 == '$ppid' { print $2 }'` do echo killing $i kill $i done done


0

Je sais que c'est vieux, mais c'est la meilleure solution que j'ai trouvée:

killtree() { 
    for p in $(pstree -p $1 | grep -o "([[:digit:]]*)" |grep -o "[[:digit:]]*" | tac);do
        echo Terminating: $p 
        kill $p
    done
}
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.