Jaki jest najlepszy sposób wysłania sygnału do wszystkich członków grupy procesów?

422

Chcę zabić całe drzewo procesów. Jaki jest najlepszy sposób, aby to zrobić przy użyciu popularnych języków skryptowych? Szukam prostego rozwiązania.

Adam Peck
źródło
3
Zombie powinny jednak odejść, gdy uruchomi się żniwiarz systemu. Przyznaję, że widziałem systemy, w których zombie pozostają, ale to nietypowe.
Brian Knoblauch
7
Czasami te długotrwałe zombie są odpowiedzialne za niektóre przerażające działania.
Użytkownik 1
Użyj jednego z poleceń chronoslub herodes.
Michael Le Barbier Grünewald
8
kill $ (pstree <PID> -p -a -l | cut -d, -f2 | cut -d '' -f1)
vrdhn
@ MichaelLeBarbierGrünewald Czy możesz link do tych programów?
mucha styropianowa

Odpowiedzi:

305

Nie mówisz, że drzewo, które chcesz zabić, jest pojedynczą grupą procesów. (Często dzieje się tak, gdy drzewo powstaje w wyniku uruchamiania serwera lub wiersza poleceń powłoki.) Grupy procesów można odkryć za pomocą GNU ps w następujący sposób:

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

Jeśli jest to grupa procesów, którą chcesz zabić, po prostu użyj kill(1)polecenia, ale zamiast nadać jej numer procesu, podaj negację numeru grupy. Na przykład, aby zabić każdy proces w grupie 5112, użyj kill -TERM -- -5112.

Norman Ramsey
źródło
3
kill -74313 -bash: kill: 74313: niepoprawna specyfikacja sygnału Jeśli dodam kill -15 -GPID, to zadziała idealnie.
Adam Peck,
51
Jak zwykle w prawie każdym poleceniu, jeśli chcesz, aby normalny argument, który zaczyna się od - nie był interpretowany jako przełącznik, poprzedź go: -: kill - -GPID
ysth
9
pgrepmoże zaoferować łatwiejszy sposób znalezienia identyfikatora grupy procesów. Na przykład, aby zabić grupę procesów my-script.sh, uruchom kill -TERM -$(pgrep -o my-script.sh).
Josh Kelley,
9
Lepiej spójrz na stackoverflow.com/questions/392022/… jest to zdecydowanie bardziej eleganckie rozwiązanie, a jeśli chcesz wymienić pids dzieci, użyj:ps -o pid --no-headers --ppid $PARENT_PID
Szymon Jeż
4
A jeśli nieznacznie zmodyfikujesz format i posortujesz, zobaczysz wszystkie procesy ładnie pogrupowane i zaczynając od (potencjalnie) rodzica grupy w każdej grupie:ps x -o "%r %p %y %x %c" | sort -nk1,2
haridsv
199

Zabij wszystkie procesy należące do tego samego drzewa procesów za pomocą ID grupy procesów ( PGID)

  • kill -- -$PGID     Użyj domyślnego sygnału ( TERM= 15)
  • kill -9 -$PGID     Użyj sygnału KILL(9)

Możesz pobrać PGIDz dowolnego ID procesu ( PID) tego samego drzewa procesów

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

Specjalne podziękowania dla tanager i Speakus za wkład w $PIDpozostałe miejsca i kompatybilność z OSX.

Wyjaśnienie

  • kill -9 -"$PGID"=> Wyślij sygnał 9 ( KILL) do wszystkich dzieci i wnuków ...
  • PGID=$(ps opgid= "$PID")=> Pobierz identyfikator grupy procesów z dowolnego identyfikatora procesu drzewa, nie tylko z identyfikatora procesu rodzica . Odmianą ps opgid= $PIDjest miejsce, w ps -o pgid --no-headers $PIDktórym pgidmożna je zastąpić pgrp.
    Ale:
    • pswstawia spacje wiodące, gdy PIDma mniej niż pięć cyfr i jest wyrównany do prawej, jak zauważył tanager . Możesz użyć:
      PGID=$(ps opgid= "$PID" | tr -d ' ')
    • psz OSX zawsze wypisuje nagłówek, dlatego Speakus proponuje:
      PGID="$( ps -o pgid "$PID" | grep [0-9] | tr -d ' ' )"
  • grep -o [0-9]* drukuje tylko kolejne cyfry (nie drukuje spacji ani nagłówków alfabetycznych).

Dalsze wiersze poleceń

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)

Ograniczenie

  • Jak zauważył przez Davide i Hubert Kario , gdy killwywoływana jest przez proces należącego do tego samego drzewa, killryzykuje się zabić przed zakończeniem całego drzewa zabijania.
  • Dlatego należy uruchomić polecenie przy użyciu procesu o innym identyfikatorze grupy procesów .

Długa historia

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

Uruchom drzewo procesów w tle za pomocą „&”

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

Polecenie pkill -P $PIDnie zabija wnuka:

> 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

Komenda kill -- -$PGIDzabija wszystkie procesy, w tym wnuka.

> 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

Wniosek

Zauważam w tym przykładzie PIDi PGIDsą równe ( 28957).
Właśnie dlatego początkowo myślałem, że kill -- -$PIDwystarczy. Ale w przypadku, gdy proces jest spawnowany w Makefileramach identyfikatora procesu, różni się on od identyfikatora grupy .

Myślę, że kill -- -$(ps -o pgid= $PID | grep -o [0-9]*)to najlepsza prosta sztuczka polegająca na zabiciu całego drzewa procesów, gdy jest wywoływana z innego identyfikatora grupy (innego drzewa procesów).

olibre
źródło
1
Cześć @davide. Dobre pytanie. Myślę, że killzawsze powinien wysyłać sygnał do całego drzewa przed otrzymaniem własnego sygnału. Ale w niektórych szczególnych okolicznościach / implementacjach killmoże wysłać sobie sygnał, zostać przerwany, a następnie odebrać własny sygnał. Jednak ryzyko powinno być wystarczająco minimalne i może być ignorowane w większości przypadków, ponieważ przed tym powinny wystąpić inne błędy. Czy to ryzyko można zignorować w twoim przypadku? Ponadto w innych odpowiedziach występuje ten wspólny błąd ( killczęść drzewa procesu jest zabijana). Mam nadzieję, że ta pomoc .. Pozdrawiam;)
olibre
3
Działa to tylko wtedy, gdy same polecenia podrzędne nie stają się liderami grup. Nawet takie proste narzędzia mantego typu. Z drugiej strony, jeśli chcesz zabić proces gandchilda z procesu potomnego, kill -- -$pidnie zadziała. Więc to nie jest ogólne rozwiązanie.
Hubert Kario,
1
Przykładem jest „dziecko” próbujące zabić swoje dzieci (czyli wnuki polecenia zainicjowanego przez użytkownika). IOW, spróbuj zabić hierarchię procesów w tle w child.sh.
Hubert Kario,
1
> kill -QUIT - "$ PGID" # taki sam sygnał jak [CRTL + C] z klawiatury ---- Aby
zakończyć, należy WYJŚĆ
1
dla opcji OSX - brak nagłówków nie jest obsługiwany, więc kod powinien zostać zaktualizowany do: PGID = "$ (ps -o pgid" $ PID "| grep [0-9] | tr -d '')"
Maxim Kholyavkin
166
pkill -TERM -P 27888

Spowoduje to zabicie wszystkich procesów, które mają identyfikator procesu nadrzędnego 27888.

Lub bardziej solidny:

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

który planuje zabić 33 sekundy później i uprzejmie prosi procesy o zakończenie.

Zobacz tę odpowiedź na zakończenie wszystkich potomków.

Onlyjob
źródło
19
W moim szybkim teście pgrep zgłosił tylko bezpośrednie dzieci, więc może to nie zabić całej hierarchii.
haridsv
10
Zgadzam się z @haridsv: pkill -Pwysyła sygnał tylko do dziecka => wnuk nie odbiera sygnału => Dlatego mam wroten inną odpowiedź, aby to wyjaśnić. Pozdrawiam ;-)
olibre
1
Ze skryptu bash, aby zabić własne dzieci, użyj pkill -TERM -P ${$}.
François Beausoleil,
3
@Olyjob, czy nie jest niebezpieczne spać, a potem zabijać? Tymczasem identyfikatory procesów mogły zostać ponownie wykorzystane przez system operacyjny: możesz zabijać procesy, które nie są już Twoimi dziećmi. Podejrzewam, że wezwanie do pkill musiałoby zostać wykonane ponownie, aby temu zapobiec.
François Beausoleil,
2
@Onlyjob FYI, jestem w stanie odrodzić 32768 procesów, jednowątkowych, z lekkim dostępem do I / O w mniej niż 19 sekund na mojej maszynie:$ time for i in {1..32768}; do ( echo $BASHPID >> pids ); done real 0m18.860s
uczestniczoi
102

Aby rekurencyjnie zabić drzewo procesów, użyj 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 $@
zhigang
źródło
3
Te --argumenty psnie działają na OS X, aby pracować tam zastąpić pskomendę: ps ax -o "pid= ppid=" | grep -E "${_regex}" | sed -E "s/${_regex}/\1/ggdzie _regexjest zdefiniowana przed forpętli:local _regex="[ ]*([0-9]+)[ ]+${_pid}"
Artur
5
Zatrzymane procesy nie są zabijane za pomocą SIGTERM. Zobacz moją odpowiedź
x-yuri,
1
-1 używa #! / Bin / bash zamiast #! / Usr / bin / env bash (lub jeszcze lepiej POSIX tylko konstruuje i / bin / sh)
Good Person
Czy wystarczy wysłać SIGKILL zamiast SIGTERM, aby zagwarantować, że killtree()działa niezawodnie?
David
3
jeśli psnie obsługuje --ppid, można pgrep -P ${_pid}zamiast tego użyć
Hubert Kario
16

Komenda rkill zpakietu pslist wysyła dany sygnał (lubSIGTERMdomyślnie) do określonego procesu i wszystkich jego potomków:

rkill [-SIG] pid/name...
Onlyjob
źródło
11

Brad też jest tym, co poleciłbym, z tym wyjątkiem, że możesz awkcałkowicie zrezygnować, jeśli skorzystasz z --ppidopcji ps.

for child in $(ps -o pid -ax --ppid $PPID) do ....... done
Kim Stebel
źródło
To nie działa dla mnie, chyba że z jakiegoś powodu wyjmę -ax (Centos5). W przeciwnym razie to świetnie!
xitrium,
9

jeśli wiesz, że przekazałeś pid procesu nadrzędnego, oto skrypt powłoki, który powinien działać:

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
źródło
Niektóre wersje ps generują ostrzeżenie, jeśli użyjesz „-ax” zamiast „ax”. Zatem: dla dziecka w $ (ps -o pid, ppid ax | \ awk "{if (\ $ 2 == $ pid) {print \ $ 1}}")
Cory R. King
9

Używam nieco zmodyfikowanej wersji metody opisanej tutaj: https://stackoverflow.com/a/5311362/563175

Wygląda to tak:

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

gdzie 24901 jest PID rodzica.

Wygląda dość brzydko, ale doskonale spełnia swoją funkcję.

Tomasz Werszko
źródło
1
Uproszczenie za pomocą grep, zamiast sed ...pstree -p 24901 | grep -oP '(?<=\()[0-9]+(?=\))'
anishsane
2
należy dodać -ldo pstreetak długie linie nie obcięta; można go uprościć również czytając kill `pstree -l -p 24901 |grep "([[:digit:]]*)" -o |tr -d '()'`(nie trzeba konwertować \ndo miejsca, ponieważ będzie działać dobrze), dzięki!
Aquarius Power
9

Zmodyfikowana wersja odpowiedzi zhiganga:

#!/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
źródło
możesz wait $pidtylko na rozpoczętych procesach, nie na wszystkich precesjach, więc nie jest to ogólne rozwiązanie
Hubert Kario
@Hubert Kario W takim przypadku czekanie zakończy się z niezerowym statusem i będzie kontynuowało wykonywanie skryptu. Czy się mylę? Ale waitukryje Terminatedwiadomość, jeśli jest to dziecko.
x-yuri
9

Nie mogę komentować (za mało reputacji), więc jestem zmuszony dodać nową odpowiedź , nawet jeśli tak naprawdę nie jest to odpowiedź.

Istnieje niewielki problem z bardzo ładną i dokładną odpowiedzią udzieloną przez @olibre 28 lutego. Wyjście ps opgid= $PIDbędzie zawierało spacje wiodące dla PID krótszego niż pięć cyfr, ponieważ psuzasadnia to kolumnę (dokładnie wyrównaj liczby). W całym wierszu polecenia powoduje to znak ujemny, następnie spacje, a następnie PID grupy. Prostym rozwiązaniem jest usunięcie potoku psz rurki tr:

kill -- -$( ps opgid= $PID | tr -d ' ' )
tanager
źródło
Dziękuję tanager. Naprawię moją odpowiedź;) Pozdrawiam
olibre
8

Aby dodać do odpowiedzi Normana Ramseya, warto spojrzeć na setsid, jeśli chcesz utworzyć grupę procesów.
http://pubs.opengroup.org/onlinepubs/009695399/functions/setsid.html

Funkcja setsid () utworzy nową sesję, jeśli proces wywołujący nie jest liderem grupy procesów. Po powrocie proces wywołujący będzie liderem sesji nowej sesji, będzie liderem grupy procesów nowej grupy procesów i nie będzie miał terminala sterującego. Identyfikator grupy procesów procesu wywołującego powinien być równy identyfikatorowi procesu wywołującego. Proces wywoływania będzie jedynym procesem w nowej grupie procesów i jedynym procesem w nowej sesji.

Co rozumiem przez to, że możesz utworzyć grupę od samego początku. Użyłem tego w php, aby móc zabić całe drzewo procesów po jego uruchomieniu.

To może być zły pomysł. Byłbym zainteresowany komentarzami.

Hajamie
źródło
W rzeczywistości jest to świetny pomysł i działa bardzo dobrze. Używam go w przypadkach, w których mogę umieścić procesy w tej samej grupie procesów (lub wszystkie są już w tej samej grupie).
sickill
7

Zainspirowany komentarzem ysth

kill -- -PGID

zamiast nadawać mu numer procesu, podaj mu negację numeru grupy. Jak zwykle w prawie każdym poleceniu, jeśli chcesz, aby zwykły argument zaczynający się od a -nie był interpretowany jako przełącznik, poprzedz go--

Steven Penny
źródło
Ups, właśnie zdałem sobie sprawę, że podałem tę samą odpowiedź, co Ty => +1. Ale ponadto wyjaśnić, jak po prostu dostać PGIDod PID. Co myślisz? Pozdrawiam
olibre
5

Poniższa funkcja powłoki jest podobna do wielu innych odpowiedzi, ale działa zarówno w systemie Linux, jak i BSD (OS X itp.) Bez zewnętrznych zależności, takich jak 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
źródło
Myślę, że masz dodatkowe słowo „dziecko” na końcu drugiej linii.
mato
@mato - To nie jest dodatkowe. Ogranicza zakres $childtej funkcji, aby nie zakłócać innych (nielokalnych) zmiennych o tej samej nazwie i zapewnić, że wartość zmiennej lokalnej zostanie wyczyszczona po zakończeniu funkcji.
Adam Katz
Och, rozumiem teraz, myślałem, że to część zadania. Chyba powinienem ponownie przeczytać (wolniej) przed napisaniem komentarza. :) Dzięki.
mato
Przy okazji, czy nie byłoby lepiej stworzyć listę dzieci, a następnie zacząć zabijać od góry (rodzica)? .. W ten sposób moglibyśmy uniknąć sytuacji, gdy rodzic odtworzy dziecko lub kontynuuje uruchamianie następnego kodu, który może potencjalnie zmienić zamierzone zachowanie.
mato
5

Bardzo łatwo jest to zrobić za pomocą Pythona za pomocą psutil . Wystarczy zainstalować psutil z pipem, a następnie masz pełny zestaw narzędzi do manipulacji procesami:

def killChildren(pid):
    parent = psutil.Process(pid)
    for child in parent.get_children(True):
        if child.is_running():
            child.terminate()
genericdave
źródło
5

Na podstawie odpowiedzi zhiganga pozwala to uniknąć samobójstwa:

init_killtree() {
    local pid=$1 child

    for child in $(pgrep -P $pid); do
        init_killtree $child
    done
    [ $pid -ne $$ ] && kill -kill $pid
}
dawid
źródło
4

Jeśli chcesz zabić proces według nazwy:

killall -9 -g someprocessname

lub

pgrep someprocessname | xargs pkill -9 -g
Mika Vatanen
źródło
3

To jest moja wersja zabijania wszystkich procesów potomnych za pomocą skryptu bash. Nie używa rekurencji i zależy od polecenia pgrep.

Posługiwać się

killtree.sh PID SIGNAL

Zawartość 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
źródło
1
To się nie powiedzie, jeśli nowe procesy zostaną utworzone po skompilowaniu listy potomnej.
Ceving
3

Oto odmiana odpowiedzi @ zhigang, która działa bez AWK, opierając się tylko na natywnych możliwościach parsowania Basha:

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

Wygląda na to, że działa dobrze zarówno na komputerach Mac, jak i Linux. W sytuacjach, w których nie można polegać na możliwości zarządzania grupami procesów - na przykład podczas pisania skryptów do testowania oprogramowania, które musi być zbudowane w wielu środowiskach - ta technika chodzenia po drzewie jest zdecydowanie pomocna.

solidsnack
źródło
1

Dzięki za mądrość, ludzie. Mój skrypt pozostawiał niektóre procesy potomne przy wyjściu, a wskazówka negacji ułatwiła wszystko. Napisałem tę funkcję do użycia w innych skryptach, jeśli to konieczne:

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

Twoje zdrowie.


źródło
1

jeśli masz pstree i perl w swoim systemie, możesz spróbować tego:

perl -e 'kill 9, (`pstree -p PID` =~ m/\((\d+)\)/sg)'
Lyman
źródło
1

Prawdopodobnie lepiej zabić rodzica przed dziećmi; w przeciwnym razie rodzic prawdopodobnie ponownie odrodzi nowe dzieci, zanim sam zostanie zabity. Przetrwają zabijanie.

Moja wersja ps różni się od powyższej; może za stary, dlatego dziwne grep ...

Używanie skryptu powłoki zamiast funkcji powłoki ma wiele zalet ...

Jest to jednak w zasadzie pomysł zhigang


#!/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
źródło
zwróć uwagę, że skrypt @zhighang SIGSTOP kontroluje proces nadrzędny i dostarcza sygnał do zatrzymanego procesu, więc nie powinno to (AFAIK) powodować wyścigu między procesem tworzenia potomków a dostarczaniem sygnału. Twoja wersja ma jednak wyścig między uzyskaniem listy dzieci a dostarczeniem sygnału do rodzica.
Hubert Kario,
1

Poniższe testy zostały przetestowane na FreeBSD, Linux i MacOS X i zależą tylko od pgrep i kill (wersje ps -o nie działają pod BSD). Pierwszym argumentem jest pid nadrzędny, którego dzieci muszą zostać zakończone. drugi argument to wartość logiczna określająca, czy nadrzędny pid również musi zostać zakończony.

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

Spowoduje to wysłanie SIGTERM do dowolnego procesu potomnego / wnuka w skrypcie powłoki, a jeśli SIGTERM się nie powiedzie, poczeka 10 sekund, a następnie wyśle ​​kill.


Wcześniejsza odpowiedź:

Poniższe działa również, ale zabije samą powłokę na 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
Orsiris de Jong
źródło
1

Rozwijam dalej rozwiązania zhigang, xyuri i 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

Ta wersja pozwoli uniknąć zabijania jego przodków - co powoduje zalew procesów potomnych w poprzednich rozwiązaniach.

Procesy są odpowiednio zatrzymywane przed ustaleniem listy potomnej, aby żadne nowe elementy potomne nie zostały utworzone ani zniknęły.

Po zabiciu zatrzymane zadania muszą nadal znikać z systemu.

Peter Steier
źródło
1

Stare pytanie, wiem, ale wszystkie odpowiedzi wydają się wciąż nazywać ps, co mi się nie podobało.

To rozwiązanie oparte na awk nie wymaga rekurencji i wywołuje ps tylko raz.

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

Lub w jednym wierszu:

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

Zasadniczo chodzi o to, że tworzymy tablicę (a) elementu nadrzędnego: wpisy podrzędne, a następnie zapętlamy tablicę, szukając dzieci dla naszych pasujących rodziców, dodając je do naszej listy nadrzędnej (p).

Jeśli nie chcesz zabić procesu najwyższego poziomu, to rób

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

tuż przed tym, jak linia system () usunie go z zestawu zabić.

Pamiętaj, że jest tutaj warunek wyścigu, ale jest to prawda (o ile widzę) wszystkich rozwiązań. Robi to, czego potrzebowałem, ponieważ skrypt, dla którego go potrzebowałem, nie tworzy wielu krótkotrwałych dzieci.

Ćwiczeniem dla czytelnika byłoby uczynienie z niego pętli 2-przebiegowej: po pierwszym przejściu wyślij SIGSTOP do wszystkich procesów na liście p, następnie zapętl, aby ponownie uruchomić ps, a po drugim przejściu wyślij SIGTERM, a następnie SIGCONT. Jeśli nie zależy ci na ładnych zakończeniach, drugie przejście może być po prostu SIGKILL.

Whinger
źródło
0

Jeśli znasz pid rzeczy, którą chcesz zabić, zwykle możesz przejść z identyfikatora sesji i wszystkiego w tej samej sesji. Sprawdziłbym dwa razy, ale użyłem tego do skryptów uruchamiających rsyncs w pętlach, które chcę umrzeć, a nie uruchamiających kolejnych (z powodu pętli), tak jak wtedy, gdybym po prostu zabił wszystkie rsync.

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

Jeśli nie znasz pid, możesz zagnieżdżać więcej

kill $(ps -o pid= -s $(ps -o sess --no-heading --pid $(pgrep rsync )))
Morgan
źródło
0
ps -o pid= --ppid $PPID | xargs kill -9 
chrześcijanin
źródło
12
Nie kill -9, naprawdę.
Nathan Kidd
2
Czasami kill -15nie pomoże.
Fedir RYKHTIK,
0

W sh polecenie zadań wyświetli listę procesów w tle. W niektórych przypadkach może być lepiej zabić najpierw najnowszy proces, np. Starszy stworzył wspólne gniazdo. W takich przypadkach posortuj PID w odwrotnej kolejności. Czasami chcesz poczekać, aż zadania zapiszą coś na dysku lub coś takiego, zanim przestaną.

I nie zabijaj, jeśli nie musisz!

for SIGNAL in TERM KILL; do
  for CHILD in $(jobs -s|sort -r); do
    kill -s $SIGNAL $CHILD
    sleep $MOMENT
  done
done
jens
źródło
0

Zabijanie procesu potomnego w skrypcie powłoki:

Wiele razy musimy zabić proces potomny, który z jakiegoś powodu został powieszony lub zablokowany. na przykład. Problem z połączeniem FTP.

Istnieją dwa podejścia,

1) Aby utworzyć osobnego nowego rodzica dla każdego dziecka, które będzie monitorować i zabijać proces potomny, gdy upłynie limit czasu.

Utwórz test.sh w następujący sposób,

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

i obserwuj procesy o nazwie „test” w innym terminalu za pomocą następującego polecenia.

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

Powyższy skrypt utworzy 4 nowe procesy potomne i ich rodziców. Każdy proces potomny będzie działał przez 10 sekund. Ale gdy osiągnie limit 5 sekund, odpowiednie procesy nadrzędne zabiją te dzieci. Dlatego dziecko nie będzie w stanie ukończyć wykonywania (10 sekund). Rozegraj te czasy (przełączniki 10 i 5), aby zobaczyć inne zachowanie. W takim przypadku dziecko zakończy wykonywanie w 5 sekund, zanim osiągnie limit 10 sekund.

2) Pozwól, aby bieżący rodzic monitorował i zabił proces potomny, gdy upłynie limit czasu. To nie stworzy osobnego rodzica do monitorowania każdego dziecka. Ponadto możesz poprawnie zarządzać wszystkimi procesami potomnymi w obrębie tego samego rodzica.

Utwórz test.sh w następujący sposób,

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

i obserwuj procesy o nazwie „test” w innym terminalu za pomocą następującego polecenia.

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

Powyższy skrypt utworzy 4 nowe procesy potomne. Przechowujemy pids wszystkich procesów potomnych i zapętlamy je, aby sprawdzić, czy zakończyły się ich wykonywanie, czy nadal działają. Proces potomny będzie wykonywany do czasu CMD_TIME. Ale jeśli przekroczony zostanie limit czasu CNT_TIME_OUT, wszystkie dzieci zostaną zabite przez proces nadrzędny. Możesz zmienić czas i bawić się skryptem, aby zobaczyć zachowanie. Wadą tego podejścia jest użycie identyfikatora grupy do zabicia całego drzewa potomnego. Ale sam proces macierzysty należy do tej samej grupy, więc również zostanie zabity.

Może być konieczne przypisanie innego identyfikatora grupy procesowi nadrzędnemu, jeśli nie chcesz, aby rodzic został zabity.

Więcej informacji można znaleźć tutaj,

Zabijanie procesu potomnego w skrypcie powłoki

Hemant Thorat
źródło
0

Ten skrypt działa również:

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

Nullpointer
źródło
0

Wiem, że to stare, ale to lepsze rozwiązanie, które znalazłem:

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