Was ist der beste Weg, um ein Signal an alle Mitglieder einer Prozessgruppe zu senden?

422

Ich möchte einen ganzen Prozessbaum töten. Was ist der beste Weg, dies mit gängigen Skriptsprachen zu tun? Ich suche eine einfache Lösung.

Adam Peck
quelle
3
Zombies sollten jedoch verschwinden, wenn der Systemschnitter läuft. Ich gebe zu, dass ich Systeme gesehen habe, in denen Zombies verweilen, aber das ist untypisch.
Brian Knoblauch
7
Manchmal sind diese verweilenden Zombies für eine beängstigende Aktivität verantwortlich.
User1
Verwenden Sie einen der Befehle chronosoder herodes.
Michael Le Barbier Grünewald
8
kill $ (pstree <PID> -p -a -l | cut -d, -f2 | cut -d '' -f1)
vrdhn
@ MichaelLeBarbierGrünewald Könnten Sie bitte auf diese Programme verlinken?
Styropor fliegen

Antworten:

305

Sie sagen nicht, ob der Baum, den Sie töten möchten, eine einzelne Prozessgruppe ist. (Dies ist häufig der Fall, wenn der Baum das Ergebnis eines Forkings von einem Serverstart oder einer Shell-Befehlszeile ist.) Sie können Prozessgruppen mithilfe von GNU ps wie folgt ermitteln:

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

Wenn es sich um eine Prozessgruppe handelt, die Sie beenden möchten, verwenden Sie einfach den kill(1)Befehl. Geben Sie jedoch anstelle einer Prozessnummer die Negation der Gruppennummer an. Verwenden Sie beispielsweise, um jeden Prozess in Gruppe 5112 abzubrechen kill -TERM -- -5112.

Norman Ramsey
quelle
3
kill -74313 -bash: kill: 74313: ungültige Signalspezifikation Wenn ich die kill -15 -GPID hinzufüge, hat es perfekt funktioniert.
Adam Peck
51
Wie bei fast jedem Befehl üblich, sollten Sie ein normales Argument, das mit einem - beginnt und nicht als Schalter interpretiert werden soll, mit -: kill - -GPID
ysth
9
pgrepkann eine einfachere Möglichkeit bieten, die Prozessgruppen-ID zu finden. Führen Sie beispielsweise aus, um die Prozessgruppe von my-script.sh zu beenden kill -TERM -$(pgrep -o my-script.sh).
Josh Kelley
9
Schauen Sie sich besser stackoverflow.com/questions/392022/ an. Es ist eine weitaus elegantere Lösung. Wenn Sie die Pids der Kinder auflisten müssen, verwenden Sie:ps -o pid --no-headers --ppid $PARENT_PID
Szymon Jeż
4
Und wenn Sie das Format leicht ändern und sortieren, sehen Sie alle Prozesse schön gruppiert und beginnen mit (möglicherweise) dem übergeordneten Gruppenmitglied in jeder Gruppe:ps x -o "%r %p %y %x %c" | sort -nk1,2
haridsv
199

Beenden Sie alle Prozesse, die zum selben Prozessbaum gehören, mit der Prozessgruppen-ID ( PGID)

  • kill -- -$PGID     Standard-Signal verwenden ( TERM= 15)
  • kill -9 -$PGID     Verwende das Signal KILL(9)

Sie können die PGIDvon jeder Prozess-ID ( PID) desselben Prozessbaums abrufen

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

Besonderer Dank geht an Tanager und Speakus für Beiträge zu $PIDverbleibenden Speicherplätzen und OSX-Kompatibilität.

Erläuterung

  • kill -9 -"$PGID"=> Signal 9 ( KILL) an alle Kinder und Enkel senden ...
  • PGID=$(ps opgid= "$PID")=> Ruft die Prozessgruppen-ID von einer beliebigen Prozess-ID des Baums ab, nicht nur von der Prozess-Eltern-ID . Eine Variation von ps opgid= $PIDist, ps -o pgid --no-headers $PIDwo pgiddurch ersetzt werden kann pgrp.
    Aber:
    • psFügt führende Leerzeichen ein, wenn sie PIDweniger als fünf Stellen haben und rechts ausgerichtet sind, wie vom Tanager bemerkt . Sie können verwenden:
      PGID=$(ps opgid= "$PID" | tr -d ' ')
    • psVon OSX aus drucken Sie immer den Header, daher schlägt Speakus vor:
      PGID="$( ps -o pgid "$PID" | grep [0-9] | tr -d ' ' )"
  • grep -o [0-9]* druckt nur aufeinanderfolgende Ziffern (druckt keine Leerzeichen oder alphabetischen Überschriften).

Weitere Kommandozeilen

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)

Verjährung

  • Wie von David und Hubert Kario bemerkt , wenn er killvon einem Prozess aufgerufen wird, der zum selben Baum gehört,kill Gefahr, dass sie sich selbst töten bevor die gesamte beendet wird.
  • Stellen Sie daher sicher, dass Sie den Befehl mit einem Prozess ausführen, der eine andere Prozessgruppen-ID hat .

Lange Geschichte

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

Führen Sie den Prozessbaum im Hintergrund mit '&' aus.

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

Der Befehl pkill -P $PIDtötet das Enkelkind nicht:

> 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

Der Befehl beendet kill -- -$PGIDalle Prozesse einschließlich des Enkels.

> 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

Fazit

Ich bemerke in diesem Beispiel PIDund bin PGIDgleich ( 28957).
Deshalb dachte ich ursprünglich, es kill -- -$PIDsei genug. Für den Fall, dass der Prozess innerhalb einer erzeugt wird, unterscheidet sich Makefiledie Prozess-ID von der Gruppen-ID .

Ich denke, es kill -- -$(ps -o pgid= $PID | grep -o [0-9]*)ist der beste einfache Trick, einen ganzen Prozessbaum zu töten, wenn er von einer anderen Gruppen-ID (einem anderen Prozessbaum) aufgerufen wird .

olibre
quelle
1
Hallo @davide. Gute Frage. Ich denke, ich killsollte das Signal immer an den gesamten Baum senden, bevor ich ein eigenes Signal empfange. Unter bestimmten Umständen / Implementierungen killkann das Signal jedoch an sich selbst gesendet, unterbrochen und dann ein eigenes Signal empfangen werden. Das Risiko sollte jedoch minimal genug sein und kann in den meisten Fällen ignoriert werden, da andere Fehler vor diesem auftreten sollten. Kann dieses Risiko in Ihrem Fall ignoriert werden? Darüber hinaus haben andere Antworten diesen häufigen Fehler (ein killTeil des Prozessbaums wird getötet). Hoffe diese Hilfe .. Prost;)
Olibre
3
Dies funktioniert nur, wenn die Unterbefehle selbst keine Gruppenleiter werden. Sogar solche einfachen Werkzeuge mantun das. Auf der anderen Seite funktioniert kill -- -$pides nicht , wenn Sie den gandchild-Prozess aus dem untergeordneten Prozess beenden möchten . Es ist also keine generische Lösung.
Hubert Kario
1
Das Beispiel ist das "Kind", das versucht, seine Kinder zu töten (also Enkelkinder des vom Benutzer initiierten Befehls). IOW, versuchen Sie, die Hintergrundprozesshierarchie in zu beenden child.sh.
Hubert Kario
1
> kill -QUIT - "$ PGID" # dasselbe Signal wie [CRTL + C] von der Tastatur ---- QUIT sollte durch INT ersetzt werden, um wahr zu sein
Maxim Kholyavkin
1
für die OSX-Option --no-headers wird nicht unterstützt, daher sollte der Code aktualisiert werden auf: PGID = "$ (ps -o pgid" $ PID "| grep [0-9] | tr -d '')"
Maxim Kholyavkin
166
pkill -TERM -P 27888

Dadurch werden alle Prozesse beendet, die die übergeordnete Prozess-ID 27888 haben.

Oder robuster:

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

welcher Zeitplan 33 Sekunden später tötet und die Prozesse höflich auffordert, zu beenden.

In dieser Antwort finden Sie Informationen zum Beenden aller Nachkommen.

Onlyjob
quelle
19
In meinem Schnelltest hat pgrep nur die unmittelbaren Kinder gemeldet, sodass möglicherweise nicht die gesamte Hierarchie zerstört wird.
Haridsv
10
Ich stimme @haridsv zu: pkill -Psendet das Signal nur an das Kind => das Enkelkind empfängt das Signal nicht => Deshalb habe ich eine andere Antwort geschrieben , um das zu erklären. Prost ;-)
Olibre
1
Verwenden Sie ein Bash-Skript, um Ihre eigenen Kinder zu töten pkill -TERM -P ${$}.
François Beausoleil
3
@Onlyjob, ist es nicht gefährlich zu schlafen und dann zu töten? Die Prozess-IDs wurden möglicherweise in der Zwischenzeit vom Betriebssystem wiederverwendet: Möglicherweise beenden Sie Prozesse, die nicht mehr Ihre Kinder sind. Ich vermute, dass der Aufruf von pkill erneut erfolgen müsste, um dies zu verhindern.
François Beausoleil
2
@Onlyjob FYI, ich bin in der Lage, 32768 Prozesse, Single-Threaded, mit leichtem E / A-Zugriff in weniger als 19 Sekunden auf meinem Computer zu $ time for i in {1..32768}; do ( echo $BASHPID >> pids ); done real 0m18.860s
erzeugen
102

Verwenden Sie killtree (), um einen Prozessbaum rekursiv zu beenden:

#!/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 $@
Zhigang
quelle
3
Die --Argumente, psum unter OS X nicht zu funktionieren. Damit es dort funktioniert, ersetzen Sie den psBefehl durch: ps ax -o "pid= ppid=" | grep -E "${_regex}" | sed -E "s/${_regex}/\1/gwo _regexvor der forSchleife definiert ist :local _regex="[ ]*([0-9]+)[ ]+${_pid}"
artur
5
Gestoppte Prozesse werden mit SIGTERM nicht beendet. Siehe meine Antwort
x-yuri
1
-1 verwendet #! / Bin / bash anstelle von #! / Usr / bin / env bash (oder besser noch POSIX nur Konstrukte und / bin / sh)
Gute Person
Würde es ausreichen, anstelle von SIGTERM ein SIGKILL zu senden, um sicherzustellen, dass dies killtree()zuverlässig funktioniert?
David
3
Wenn psnicht unterstützt --ppid, kann man pgrep -P ${_pid}stattdessen verwenden
Hubert Kario
16

Der Befehl rkill aus dem pslist- Paket sendet ein bestimmtes Signal (oderSIGTERMstandardmäßig) an den angegebenen Prozess und alle seine Nachkommen:

rkill [-SIG] pid/name...
Onlyjob
quelle
11

Brads Antwort würde ich auch empfehlen, außer dass Sie sie awkganz abschaffen können, wenn Sie die --ppidOption dazu verwenden ps.

for child in $(ps -o pid -ax --ppid $PPID) do ....... done
Kim Stebel
quelle
Dies funktioniert bei mir nur, wenn ich aus irgendeinem Grund die -ax herausnehme (Centos5). Ansonsten ist das super!
Xitrium
9

Wenn Sie wissen, dass Sie die PID des übergeordneten Prozesses übergeben haben, finden Sie hier ein Shell-Skript, das funktionieren sollte:

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
brad.lane
quelle
Einige Versionen von ps geben eine Warnung aus, wenn Sie "-ax" anstelle von "ax" verwenden. Also: für Kind in $ (ps -o pid, ppid ax | \ awk "{if (\ $ 2 == $ pid) {print \ $ 1}}")
Cory R. King
9

Ich verwende eine etwas modifizierte Version einer hier beschriebenen Methode: https://stackoverflow.com/a/5311362/563175

So sieht es aus:

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

Dabei ist 24901 die PID des Elternteils.

Es sieht ziemlich hässlich aus, macht aber seinen Job perfekt.

Tomasz Werszko
quelle
1
Vereinfachen mit grep, anstatt sed ...pstree -p 24901 | grep -oP '(?<=\()[0-9]+(?=\))'
anishsane
2
Sie sollte noch hinzufügen -lzu pstree, so lange Linien erhalten abgeschnitten nicht; es kann einfacher gemacht werden, auch mit zu lesen kill `pstree -l -p 24901 |grep "([[:digit:]]*)" -o |tr -d '()'`(keine Notwendigkeit, \nin den Raum zu konvertieren , da es gut funktioniert), danke!
Wassermann Power
9

Geänderte Version von Zhigangs Antwort:

#!/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
x-yuri
quelle
Sie können wait $pidnur auf Prozesse, die Sie gestartet haben, nicht alle Prozesse, so dass dies keine generische Lösung ist
Hubert Kario
@ Hubert Kario In diesem Fall wird wait nur mit einem Status ungleich Null beendet und das Skript weiter ausgeführt. Liege ich falsch? Aber waitwird unterdrücken TerminatedNachricht , wenn es ein Kind ist.
X-Yuri
9

Ich kann nicht kommentieren (nicht genug Ruf), daher bin ich gezwungen, eine neue Antwort hinzuzufügen , obwohl dies nicht wirklich eine Antwort ist.

Es gibt ein kleines Problem mit der ansonsten sehr schönen und gründlichen Antwort von @olibre am 28. Februar. Die Ausgabe von ps opgid= $PIDenthält führende Leerzeichen für eine PID mit weniger als fünf Ziffern, da dies psdie Spalte rechtfertigt (die Zahlen genau ausrichten). Innerhalb der gesamten Befehlszeile führt dies zu einem negativen Vorzeichen, gefolgt von Leerzeichen, gefolgt von der Gruppen-PID. Einfache Lösung ist mit dem Rohr pszu trzu entfernen Plätzen:

kill -- -$( ps opgid= $PID | tr -d ' ' )
Tanager
quelle
Danke Tanager. Ich werde meine Antwort
korrigieren
8

Um die Antwort von Norman Ramsey zu ergänzen, lohnt es sich möglicherweise, sich setsid anzusehen, wenn Sie eine Prozessgruppe erstellen möchten.
http://pubs.opengroup.org/onlinepubs/009695399/functions/setsid.html

Die Funktion setsid () erstellt eine neue Sitzung, wenn der aufrufende Prozess kein Prozessgruppenleiter ist. Bei der Rückkehr ist der aufrufende Prozess der Sitzungsleiter dieser neuen Sitzung, der Prozessgruppenleiter einer neuen Prozessgruppe und hat kein kontrollierendes Terminal. Die Prozessgruppen-ID des aufrufenden Prozesses wird gleich der Prozess-ID des aufrufenden Prozesses gesetzt. Der aufrufende Prozess ist der einzige Prozess in der neuen Prozessgruppe und der einzige Prozess in der neuen Sitzung.

Was ich damit meine, dass Sie vom Startvorgang an eine Gruppe erstellen können. Ich habe dies in PHP verwendet, um einen ganzen Prozessbaum nach dem Start töten zu können.

Dies kann eine schlechte Idee sein. Ich würde mich für Kommentare interessieren.

Hajamie
quelle
Eigentlich ist das eine tolle Idee und funktioniert sehr gut. Ich verwende es in Fällen, in denen ich Prozesse in dieselbe Prozessgruppe einordnen kann (oder sie sind bereits in derselben Gruppe).
sickill
7

Inspiriert von Ysths Kommentar

kill -- -PGID

Anstatt ihm eine Prozessnummer zu geben, geben Sie ihm die Negation der Gruppennummer. Wie bei fast jedem Befehl üblich, sollten Sie ein normales Argument, das mit a beginnt -und nicht als Schalter interpretiert werden soll, voranstellen--

Steven Penny
quelle
Hoppla, ich habe gerade festgestellt, dass ich die gleiche Antwort wie Sie gegeben habe => +1. Aber darüber hinaus erkläre ich , wie einfach zu bekommen PGIDaus PID. Was denken Sie? Cheers
Olibre
5

Die folgende Shell-Funktion ähnelt vielen anderen Antworten, funktioniert jedoch sowohl unter Linux als auch unter BSD (OS X usw.) ohne externe Abhängigkeiten wie 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
}
David Röthlisberger
quelle
Ich denke, Sie haben ein zusätzliches Wort "Kind" am Ende der 2. Zeile.
Mato
@mato - Das ist kein Extra. Es beschränkt den Umfang $childdieser Funktion, um andere (nicht lokale) Variablen mit demselben Namen nicht zu stören und um sicherzustellen, dass der Wert der lokalen Variablen nach Beendigung der Funktion bereinigt wird.
Adam Katz
Oh, ich verstehe jetzt, ich dachte, es wäre Teil der Aufgabe. Ich denke, ich sollte noch einmal lesen (langsamer), bevor ich einen Kommentar schreibe. :) Vielen Dank.
Mato
Übrigens, wäre es nicht besser, eine Liste von Kindern zu erstellen und dann von oben (Eltern) zu töten? Auf diese Weise können wir eine Situation vermeiden, in der ein Elternteil ein Kind neu erstellt oder den nächsten Code weiter ausführt, wodurch möglicherweise das beabsichtigte Verhalten geändert wird.
Mato
5

Mit Python mit Psutil ist das ganz einfach . Installieren Sie einfach psutil mit pip und Sie haben eine vollständige Suite von Prozessmanipulations-Tools:

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

Basierend auf Zhigangs Antwort vermeidet dies Selbsttötung:

init_killtree() {
    local pid=$1 child

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

Wenn Sie einen Prozess mit Namen beenden möchten:

killall -9 -g someprocessname

oder

pgrep someprocessname | xargs pkill -9 -g
Mika Vatanen
quelle
3

Dies ist meine Version des Beendens aller untergeordneten Prozesse mit dem Bash-Skript. Es verwendet keine Rekursion und hängt vom Befehl pgrep ab.

Verwenden

killtree.sh PID SIGNAL

Inhalt von 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//,/ }
marekdef
quelle
1
Dies schlägt fehl, wenn nach dem Erstellen der untergeordneten Liste neue Prozesse erstellt werden.
20.
3

Hier ist eine Variation von @ zhigangs Antwort, die ohne AWK auskommt und sich nur auf Bashs native Parsing-Möglichkeiten stützt:

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"
}

Es scheint sowohl auf Macs als auch auf Linux gut zu funktionieren. In Situationen, in denen Sie sich nicht darauf verlassen können, Prozessgruppen verwalten zu können - beispielsweise beim Schreiben von Skripten zum Testen einer Software, die in mehreren Umgebungen erstellt werden muss - ist diese Tree-Walking-Technik auf jeden Fall hilfreich.

Solidsnack
quelle
1

Danke für deine Weisheit, Leute. Mein Skript hinterließ beim Beenden einige untergeordnete Prozesse, und der Negationstipp erleichterte die Arbeit. Ich habe diese Funktion geschrieben, um sie bei Bedarf in anderen Skripten zu verwenden:

# 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
}

Prost.


quelle
1

Wenn Sie pstree und perl auf Ihrem System haben, können Sie Folgendes versuchen:

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

Es ist wahrscheinlich besser, die Eltern vor den Kindern zu töten; Andernfalls kann der Elternteil wahrscheinlich wieder neue Kinder hervorbringen, bevor er selbst getötet wird. Diese werden das Töten überleben.

Meine Version von ps unterscheidet sich von der oben genannten; vielleicht zu alt, deshalb das seltsame grepping ...

Die Verwendung eines Shell-Skripts anstelle einer Shell-Funktion hat viele Vorteile ...

Es ist jedoch im Grunde Zhigangs Idee


#!/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
Peter Steier
quelle
Beachten Sie, dass das @ zhighang-Skript den übergeordneten Prozess SIGSTOPT und ein Signal an den gestoppten Prozess liefert. Dies sollte also nicht (AFAIK) zu einer Race-Bedingung zwischen dem Erstellen von untergeordneten Prozessen und der Signalübermittlung führen. Ihre Version hat jedoch einen Wettlauf zwischen dem Abrufen der Liste der Kinder und der Signalübermittlung an die Eltern.
Hubert Kario
1

Das Folgende wurde unter FreeBSD, Linux und MacOS X getestet und hängt nur von pgrep und kill ab (die ps-o-Versionen funktionieren nicht unter BSD). Das erste Argument ist die Eltern-PID, deren Kinder beendet werden müssen. Das zweite Argument ist ein Boolescher Wert, um zu bestimmen, ob die übergeordnete PID ebenfalls beendet werden muss.

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

Dadurch wird SIGTERM an einen untergeordneten Prozess innerhalb eines Shell-Skripts gesendet. Wenn SIGTERM nicht erfolgreich ist, wartet es 10 Sekunden und sendet dann kill.


Frühere Antwort:

Das Folgende funktioniert auch, wird aber die Shell selbst auf BSD töten.

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
Orsiris de Jong
quelle
1

Ich entwickle die Lösung von Zhigang, Xyuri und Solidsneck weiter:

 #!/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

Diese Version vermeidet das Töten ihrer Vorfahren - was in den vorherigen Lösungen zu einer Flut von untergeordneten Prozessen führt.

Prozesse werden ordnungsgemäß gestoppt, bevor die untergeordnete Liste festgelegt wird, sodass keine neuen untergeordneten Elemente erstellt werden oder verschwinden.

Nach dem Tod müssen die gestoppten Jobs weiterhin aus dem System verschwinden.

Peter Steier
quelle
1

Alte Frage, ich weiß, aber alle Antworten scheinen weiterhin ps zu nennen, was mir nicht gefallen hat.

Diese awk-basierte Lösung erfordert keine Rekursion und ruft ps nur einmal auf.

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)
}'

Oder in einer Zeile:

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)}'

Grundsätzlich besteht die Idee darin, ein Array (a) von Parent: Child-Einträgen aufzubauen und dann das Array zu durchlaufen, um Kinder für unsere übereinstimmenden Eltern zu finden, und sie im Laufe der Zeit zu unserer Elternliste (p) hinzuzufügen.

Wenn Sie den Prozess der obersten Ebene nicht beenden möchten, tun Sie dies

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

kurz bevor die system () - Zeile sie aus dem Kill-Set entfernen würde.

Denken Sie daran, dass es hier eine Rennbedingung gibt, aber das gilt (soweit ich sehen kann) für alle Lösungen. Es macht das, was ich brauchte, weil das Skript, für das ich es brauchte, nicht viele kurzlebige Kinder schafft.

Eine Übung für den Leser wäre, es zu einer 2-Pass-Schleife zu machen: Senden Sie nach dem ersten Durchgang SIGSTOP an alle Prozesse in der p-Liste, dann eine Schleife, um ps erneut auszuführen, und senden Sie nach dem zweiten Durchgang SIGTERM und dann SIGCONT. Wenn Sie sich nicht für schöne Enden interessieren, könnte der zweite Durchgang einfach SIGKILL sein, nehme ich an.

Whinger
quelle
0

Wenn Sie die PID der Sache kennen, die Sie töten möchten, können Sie normalerweise von der Sitzungs-ID und von allem in derselben Sitzung ausgehen. Ich würde es noch einmal überprüfen, aber ich habe dies für Skripte verwendet, die rsyncs in Schleifen starten, die ich sterben möchte, und keine andere (wegen der Schleife) starten, als würde ich nur rsync töten.

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

Wenn Sie die PID nicht kennen, können Sie immer noch mehr nisten

kill $(ps -o pid= -s $(ps -o sess --no-heading --pid $(pgrep rsync )))
Morgan
quelle
0
ps -o pid= --ppid $PPID | xargs kill -9 
Christian
quelle
12
Nicht kill -9wirklich.
Nathan Kidd
2
Manchmal kill -15hilft es nicht.
Fedir RYKHTIK
0

In sh listet der Befehl jobs die Hintergrundprozesse auf. In einigen Fällen ist es möglicherweise besser, zuerst den neuesten Prozess abzubrechen, z. B. hat der ältere einen gemeinsam genutzten Socket erstellt. In diesen Fällen sortieren Sie die PIDs in umgekehrter Reihenfolge. Manchmal möchten Sie einen Moment warten, bis die Jobs etwas auf die Festplatte oder ähnliches geschrieben haben, bevor sie beendet werden.

Und töte nicht, wenn du nicht musst!

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

Untergeordneten Prozess im Shell-Skript beenden:

Oft müssen wir untergeordnete Prozesse beenden, die aus irgendeinem Grund gehängt oder blockiert werden. z.B. Problem mit der FTP-Verbindung.

Es gibt zwei Ansätze:

1) Erstellen eines separaten neuen Elternteils für jedes Kind, das den untergeordneten Prozess überwacht und beendet, sobald das Zeitlimit erreicht ist.

Erstellen Sie test.sh wie folgt:

#!/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;

und beobachten Sie Prozesse, die in einem anderen Terminal den Namen 'test' haben, mit dem folgenden Befehl.

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

Das obige Skript erstellt 4 neue untergeordnete Prozesse und deren Eltern. Jeder untergeordnete Prozess wird 10 Sekunden lang ausgeführt. Sobald jedoch eine Zeitüberschreitung von 5 Sekunden erreicht ist, werden diese Kinder durch ihre jeweiligen übergeordneten Prozesse getötet. Das Kind kann die Ausführung also nicht abschließen (10 Sekunden). Spielen Sie mit diesen Timings (Schalter 10 und 5), um ein anderes Verhalten zu sehen. In diesem Fall beendet das Kind die Ausführung in 5 Sekunden, bevor das Zeitlimit von 10 Sekunden erreicht ist.

2) Lassen Sie den aktuellen übergeordneten Prozess überwachen und beenden, sobald das Timeout erreicht ist. Dadurch wird kein separates übergeordnetes Element erstellt, um jedes untergeordnete Element zu überwachen. Außerdem können Sie alle untergeordneten Prozesse innerhalb desselben übergeordneten Prozesses ordnungsgemäß verwalten.

Erstellen Sie test.sh wie folgt:

#!/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;

und beobachten Sie Prozesse, die in einem anderen Terminal den Namen 'test' haben, mit dem folgenden Befehl.

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

Das obige Skript erstellt 4 neue untergeordnete Prozesse. Wir speichern Pids aller untergeordneten Prozesse und durchlaufen sie, um zu überprüfen, ob sie ausgeführt wurden oder noch ausgeführt werden. Der untergeordnete Prozess wird bis zur CMD_TIME-Zeit ausgeführt. Wenn das Zeitlimit für CNT_TIME_OUT erreicht ist, werden alle Kinder vom übergeordneten Prozess getötet. Sie können das Timing wechseln und mit dem Skript herumspielen, um das Verhalten zu sehen. Ein Nachteil dieses Ansatzes besteht darin, dass die Gruppen-ID zum Töten aller untergeordneten Bäume verwendet wird. Der übergeordnete Prozess selbst gehört jedoch zur selben Gruppe, sodass er auch getötet wird.

Möglicherweise müssen Sie dem übergeordneten Prozess eine andere Gruppen-ID zuweisen, wenn Sie nicht möchten, dass das übergeordnete Element getötet wird.

Weitere Details finden Sie hier,

Untergeordneten Prozess im Shell-Skript beenden

Hemant Thorat
quelle
0

Dieses Skript funktioniert auch:

#/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

Null Zeiger
quelle
0

Ich weiß, dass das alt ist, aber das ist die bessere Lösung, die ich gefunden habe:

killtree() { 
    for p in $(pstree -p $1 | grep -o "([[:digit:]]*)" |grep -o "[[:digit:]]*" | tac);do
        echo Terminating: $p 
        kill $p
    done
}
Luciano Andress Martini
quelle