Qual è il modo migliore per inviare un segnale a tutti i membri di un gruppo di processi?


422

Voglio uccidere un intero albero di processo. Qual è il modo migliore per farlo usando qualsiasi linguaggio di scripting comune? Sto cercando una soluzione semplice.


3
Gli zombi dovrebbero sparire quando viene eseguito il mietitore di sistema. Devo ammettere di aver visto sistemi in cui gli zombi persistono, ma questo è atipico.
Brian Knoblauch,

7
A volte quegli zombi persistenti sono responsabili di alcune attività spaventose.
Utente 1

Utilizzare uno dei comandi chronoso herodes.
Michael Le Barbier Grünewald,

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

@ MichaelLeBarbierGrünewald Potresti collegarti a questi programmi?
polistirolo vola il

Risposte:


305

Non dici se l'albero che vuoi uccidere è un singolo gruppo di processi. (Questo è spesso il caso se l'albero è il risultato di un fork dall'avvio di un server o da una riga di comando della shell.) Puoi scoprire i gruppi di processi usando GNU ps come segue:

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

Se si tratta di un gruppo di processi che si desidera eliminare, utilizzare semplicemente il kill(1)comando ma invece di assegnargli un numero di processo, assegnare la negazione del numero di gruppo. Ad esempio, per interrompere tutti i processi nel gruppo 5112, utilizzare kill -TERM -- -5112.


3
kill -74313 -bash: kill: 74313: specifica del segnale non valida Se aggiungo kill -15 -GPID ha funzionato perfettamente.
Adam Peck,

51
Come al solito con quasi tutti i comandi, se si desidera un normale argomento che inizia con un - per non essere interpretato come un interruttore, precederlo con -: kill - -GPID
ysth

9
pgreppuò offrire un modo più semplice per trovare l'ID del gruppo di processi. Ad esempio, per uccidere il gruppo di processi di my-script.sh, esegui kill -TERM -$(pgrep -o my-script.sh).
Josh Kelley,

9
Meglio guardare stackoverflow.com/questions/392022/... è di gran lunga una soluzione più elegante e se avete bisogno di elencare i pid dei bambini quindi utilizzare:ps -o pid --no-headers --ppid $PARENT_PID
Szymon Jez

4
E se si modifica leggermente il formato e si ordina, si arriva a vedere tutti i processi ben raggruppati e che iniziano (potenzialmente) il genitore del gruppo in ciascun gruppo:ps x -o "%r %p %y %x %c" | sort -nk1,2
haridsv

199

Uccidere tutti i processi appartenenti allo stesso albero dei processi utilizzando l' ID gruppo di processi ( PGID)

  • kill -- -$PGID     Usa segnale predefinito ( TERM= 15)
  • kill -9 -$PGID     Usa il segnale KILL(9)

È possibile recuperare PGIDda qualsiasi Process-ID ( PID) dello stesso albero dei processi

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

Un ringraziamento speciale a tanager e Speakus per i contributi sugli $PIDspazi rimanenti e la compatibilità OSX.

Spiegazione

  • kill -9 -"$PGID"=> Invia il segnale 9 ( KILL) a tutti i figli e nipoti ...
  • PGID=$(ps opgid= "$PID")=> Recupera l' ID gruppo di processo da qualsiasi ID processo dell'albero, non solo l' ID padre-processo . Una variante di ps opgid= $PIDè ps -o pgid --no-headers $PIDdove pgidpuò essere sostituita da pgrp.
    Ma:
    • psinserisce gli spazi iniziali quando PIDè inferiore a cinque cifre e allineati a destra come notato dal tanager . Puoi usare:
      PGID=$(ps opgid= "$PID" | tr -d ' ')
    • psda OSX stampare sempre l'intestazione, quindi Speakus propone:
      PGID="$( ps -o pgid "$PID" | grep [0-9] | tr -d ' ' )"
  • grep -o [0-9]* stampa solo cifre successive (non stampa spazi o intestazioni alfabetiche).

Ulteriori linee di comando

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)

Limitazione

  • Come notato da David e Hubert Kario , quando killviene invocato da un processo appartenente allo stesso albero, killrischia di uccidersi prima di terminare l'uccisione dell'intero albero.
  • Pertanto, assicurarsi di eseguire il comando utilizzando un processo con un ID gruppo di processo diverso .

Lunga storia

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

Esegui l'albero dei processi in background usando '&'

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

Il comando pkill -P $PIDnon uccide il nipote:

> 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

Il comando kill -- -$PGIDuccide tutti i processi incluso il nipote.

> 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

Conclusione

Ho notato in questo esempio PIDe PGIDsono equal ( 28957).
Questo è il motivo per cui inizialmente pensavo kill -- -$PIDfosse abbastanza. Ma nel caso il processo è depongono le uova all'interno di un Makefilel' ID di processo è diverso dal ID gruppo .

Penso che kill -- -$(ps -o pgid= $PID | grep -o [0-9]*)sia il miglior trucco semplice per uccidere un intero albero di processo quando viene chiamato da un ID gruppo diverso (un altro albero di processo).


1
Ciao @davide. Buona domanda. Penso che killdovrebbe sempre inviare il segnale all'intero albero prima di ricevere il proprio segnale. Ma in alcune circostanze / implementazioni specifiche, killpuò inviare a se stesso il segnale, essere interrotto e quindi ricevere il proprio segnale. Tuttavia, il rischio dovrebbe essere abbastanza minimo e potrebbe essere ignorato nella maggior parte dei casi perché altri bug dovrebbero verificarsi prima di questo. Questo rischio può essere ignorato nel tuo caso? Inoltre, altre risposte hanno questo bug comune ( killparte dell'albero del processo che viene ucciso). Spero che questo aiuto .. Saluti;)
olibre

3
Funziona solo se i sottocomandi stessi non diventano capigruppo. Anche strumenti così semplici come manfarlo. D'altra parte, se vuoi uccidere il processo gandchild dal processo figlio, kill -- -$pidnon funzionerà. Quindi non è una soluzione generica.
Hubert Kario,

1
L'esempio è il "bambino" che cerca di uccidere i suoi figli (così i nipoti del comando avviato dall'utente). IOW, prova a eliminare la gerarchia del processo in background child.sh.
Hubert Kario,

1
> kill -QUIT - "$ PGID" # stesso segnale di [CRTL + C] dalla tastiera ---- QUIT dovrebbe essere sostituito a INT per essere vero
Maxim Kholyavkin,

1
per l'opzione OSX --no-headers non è supportato, quindi il codice deve essere aggiornato in: PGID = "$ (ps -o pgid" $ PID "| grep [0-9] | tr -d '')"
Maxim Kholyavkin

166
pkill -TERM -P 27888

Ciò ucciderà tutti i processi che hanno l'ID processo padre 27888.

O più robusto:

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

che pianifica l'uccisione di 33 secondi dopo e cortesemente chiede ai processi di terminare.

Vedi questa risposta per terminare tutti i discendenti.


19
Nel mio test rapido, pgrep ha riportato solo i figli immediati, quindi questo potrebbe non uccidere l'intera gerarchia.
Haridsv,

10
Sono d'accordo con @haridsv: pkill -Pinvia il segnale solo al figlio => il nipote non riceve il segnale => Pertanto ho scritto un'altra risposta per spiegarlo. Saluti ;-)
olibre,

1
Da uno script bash, per uccidere i tuoi figli, usa pkill -TERM -P ${$}.
François Beausoleil,

3
@Onlyjob, non è pericoloso dormire quindi uccidere? Nel frattempo gli ID di processo potrebbero essere stati riutilizzati dal sistema operativo: potresti aver ucciso processi che non sono più tuoi figli. Sospetto che la chiamata a pkill debba essere ripetuta per evitare questo.
François Beausoleil,

2
@Olyly FYI, sono in grado di generare 32768 processi, a thread singolo, con accesso I / O leggero in meno di 19 secondi sulla mia macchina:$ time for i in {1..32768}; do ( echo $BASHPID >> pids ); done real 0m18.860s
cychoi

102

Per uccidere un albero di processo in modo ricorsivo, usa 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
Gli --argomenti per psnon funzionare su OS X. Per farlo funzionare, sostituisci il pscomando con: ps ax -o "pid= ppid=" | grep -E "${_regex}" | sed -E "s/${_regex}/\1/gdove _regexè definito prima del forciclo:local _regex="[ ]*([0-9]+)[ ]+${_pid}"
artur

5
I processi arrestati non vengono uccisi con SIGTERM. Vedi la mia risposta
x-yuri,

1
-1 usa #! / Bin / bash invece di #! / Usr / bin / env bash (o meglio ancora costruisce solo POSIX e / bin / sh)
Buona persona,

Basterebbe inviare un SIGKILL al posto di SIGTERM per garantire che funzioni in killtree()modo affidabile?
davide

3
se psnon supporta --ppid, si può usare pgrep -P ${_pid}invece
Hubert Kario

16

Il comando rkill dalpacchetto pslist invia un determinato segnale (oSIGTERMdi default) al processo specificato e a tutti i suoi discendenti:

rkill [-SIG] pid/name...

11

Anche la risposta di Brad è ciò che consiglierei, tranne per il fatto che puoi eliminare del awktutto se usi l' --ppidopzione per ps.

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

Questo non funziona per me a meno che non prenda il -ax, per qualche motivo (Centos5). Altrimenti è fantastico!
xitrium,

9

se sai passare il pid del processo genitore, ecco uno script di shell che dovrebbe funzionare:

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

Alcune versioni di ps genereranno un avviso se si utilizza "-ax" anziché "ax". Quindi: per il bambino in $ (ps -o pid, ppid ax | \ awk "{if (\ $ 2 == $ pid) {print \ $ 1}}")
Cory R. King,

9

Uso una versione leggermente modificata di un metodo descritto qui: https://stackoverflow.com/a/5311362/563175

Quindi sembra che:

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

dove 24901 è il PID del genitore.

Sembra piuttosto brutto ma fa perfettamente il suo lavoro.


1
Semplificare con grep, invece di sed ...pstree -p 24901 | grep -oP '(?<=\()[0-9]+(?=\))'
anishsane

2
dovresti aggiungere -la pstree, così le linee lunghe non vengono troncate; può essere reso più semplice da leggere anche con kill `pstree -l -p 24901 |grep "([[:digit:]]*)" -o |tr -d '()'`(non è necessario convertire \nnello spazio perché funzionerà bene), grazie!
Aquarius Power,

9

Versione modificata della risposta di 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

puoi wait $pidsolo sui processi che hai avviato, non tutte le procedure preliminari, quindi questa non è una soluzione generica
Hubert Kario

@Hubert Kario In tal caso, wait uscirà con uno stato diverso da zero e continuerà a eseguire lo script. Ho sbagliato? Ma waitsopprimerà il Terminatedmessaggio se è un bambino.
x-yuri,

9

Non posso commentare (non abbastanza reputazione), quindi sono costretto ad aggiungere una nuova risposta , anche se questa non è davvero una risposta.

C'è un leggero problema con la risposta altrimenti molto bella e approfondita fornita da @olibre il 28 febbraio. L'output di ps opgid= $PIDconterrà spazi iniziali per un PID inferiore a cinque cifre perché psgiustifica la colonna (allineare i numeri in modo rigoroso). All'interno dell'intera riga di comando, questo si traduce in un segno negativo, seguito da uno o più spazi, seguito dal gruppo PID. Soluzione semplice è quello di inviare psa tragli spazi rimuovere:

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

Grazie tanager.
Risolverò la

8

Per aggiungere alla risposta di Norman Ramsey, potrebbe essere utile esaminare setsid se si desidera creare un gruppo di processi.
http://pubs.opengroup.org/onlinepubs/009695399/functions/setsid.html

La funzione setsid () deve creare una nuova sessione, se il processo chiamante non è un leader del gruppo di processi. Al ritorno, il processo di chiamata deve essere il leader della sessione di questa nuova sessione, deve essere il leader del gruppo di processo di un nuovo gruppo di processo e non deve disporre di un terminale di controllo. L'ID del gruppo di processi del processo chiamante deve essere impostato uguale all'ID del processo chiamante. Il processo di chiamata deve essere l'unico processo nel nuovo gruppo di processi e l'unico processo nella nuova sessione.

Il che presumo significhi che è possibile creare un gruppo dal processo iniziale. L'ho usato in php per poter uccidere un intero albero di processo dopo averlo avviato.

Questa potrebbe essere una cattiva idea. Sarei interessato a commenti.


In realtà questa è un'ottima idea e funziona molto bene. Lo sto usando nei casi in cui posso mettere i processi nello stesso gruppo di processi (o sono già tutti nello stesso gruppo).
sickill

7

Ispirato dal commento di ysth

kill -- -PGID

invece di assegnargli un numero di processo, assegnagli la negazione del numero di gruppo. Come al solito con quasi tutti i comandi, se si desidera un argomento normale che inizia con un -non essere interpretato come un interruttore, precederlo con--


Oops, mi sono appena reso conto di aver dato la tua stessa risposta => +1. Ma del resto spiego come ottenere semplicemente PGIDda PID. Cosa ne pensi? Saluti
olibre

5

La seguente funzione di shell è simile a molte altre risposte, ma funziona sia su Linux che su BSD (OS X, ecc.) Senza dipendenze esterne come 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
}

Penso che tu abbia una parola in più "bambino" alla fine della seconda riga.
mato,

@mato - Non è un extra. Limita l'ambito di $childtale funzione in modo da non disturbare altre variabili (non locali) con lo stesso nome e garantire che il valore della variabile locale venga ripulito al termine della funzione.
Adam Katz,

Oh, vedo ora, pensavo facesse parte del compito. Immagino che dovrei rileggere (più lentamente) prima di scrivere un commento. :) Grazie.
mato,

A proposito, non sarebbe meglio creare un elenco di figli e poi iniziare a uccidere dall'alto (genitore)? .. Quindi potremmo evitare una situazione in cui un genitore ricrea un figlio o continua a eseguire il codice successivo che potrebbe alterare il comportamento previsto.
mato,

5

È semplicissimo farlo con Python usando psutil . Installa psutil con pip e avrai una suite completa di strumenti di manipolazione dei processi:

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

5

Basato sulla risposta di zhigang, questo evita l'omicidio:

init_killtree() {
    local pid=$1 child

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

4

Se vuoi terminare un processo per nome:

killall -9 -g someprocessname

o

pgrep someprocessname | xargs pkill -9 -g

3

Questa è la mia versione di uccidere tutti i processi figlio usando lo script bash. Non utilizza la ricorsione e dipende dal comando pgrep.

Uso

killtree.sh PID SIGNAL

Contenuto di 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
Ciò non riuscirà, se vengono creati nuovi processi dopo la creazione dell'elenco figlio.
ceving il

3

Ecco una variante della risposta di @ zhigang che funziona senza AWK, basandosi solo sulle possibilità di analisi native di 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"
}

Sembra funzionare bene su Mac e Linux. In situazioni in cui non è possibile fare affidamento sulla capacità di gestire gruppi di processi, ad esempio quando si scrivono script per testare un software che deve essere costruito in più ambienti, questa tecnica di camminata sugli alberi è sicuramente utile.


1

Grazie per la tua saggezza, gente. La mia sceneggiatura stava lasciando alcuni processi figlio all'uscita e la punta della negazione ha reso le cose più facili. Ho scritto questa funzione da utilizzare in altri script se necessario:

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

Saluti.


1

se hai pstree e perl sul tuo sistema, puoi provare questo:

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

1

Probabilmente è meglio uccidere il genitore prima dei figli; altrimenti il ​​genitore potrebbe probabilmente generare di nuovo nuovi figli prima di essere ucciso. Questi sopravviveranno all'omicidio.

La mia versione di ps è diversa da quella sopra; forse troppo vecchio, quindi lo strano grepping ...

Utilizzare uno script di shell anziché una funzione di shell presenta molti vantaggi ...

Tuttavia, è fondamentalmente un'idea di 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

si noti che lo script @zhighang SIGSTOP esegue il processo principale e invia il segnale al processo interrotto, quindi ciò non dovrebbe (AFAIK) causare una condizione di competizione tra il processo di creazione dei figli e la consegna del segnale. La tua versione ha comunque una corsa tra ottenere l'elenco dei figli e segnalare la consegna al genitore.
Hubert Kario,

1

Quanto segue è stato testato su FreeBSD, Linux e MacOS X e dipende solo da pgrep e kill (le versioni ps -o non funzionano con BSD). Il primo argomento è il pid genitore di cui devono essere terminati i figli. il secondo argomento è un valore booleano per determinare se anche il pid padre deve essere terminato.

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

Questo invierà SIGTERM a qualsiasi processo figlio / nipote all'interno di uno script di shell e se SIGTERM non riesce, attenderà 10 secondi e quindi invierà kill.


Prima risposta:

Di seguito funziona anche ma ucciderà la shell stessa su 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

Sviluppo ulteriormente la soluzione di zhigang, xyuri e 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

Questa versione eviterà di uccidere i suoi antenati, il che provoca un'inondazione di processi figlio nelle soluzioni precedenti.

I processi vengono arrestati correttamente prima di determinare l'elenco figlio, in modo che non vengano creati o scompaiano nuovi figli.

Dopo essere stati uccisi, i lavori interrotti devono continuare a scomparire dal sistema.


1

Vecchia domanda, lo so, ma tutte le risposte sembrano continuare a chiamare ps, cosa che non mi è piaciuta.

Questa soluzione basata su awk non richiede ricorsione e chiama ps solo una volta.

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

O su una riga singola:

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

Fondamentalmente l'idea è che costruiamo un array (a) di parent: voci figlio, quindi giriamo attorno all'array alla ricerca di figli per i nostri genitori corrispondenti, aggiungendoli all'elenco dei genitori (p) mentre procediamo.

Se non vuoi uccidere il processo di livello superiore, allora fallo

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

appena prima che la riga system () lo rimuovesse dal kill set.

Tieni presente che qui c'è una condizione di razza, ma è vero (per quanto posso vedere) di tutte le soluzioni. Fa quello che mi serviva perché la sceneggiatura di cui avevo bisogno non creava molti bambini di breve durata.

Un esercizio per il lettore sarebbe quello di renderlo un ciclo a 2 passaggi: dopo il primo passaggio, inviare SIGSTOP a tutti i processi nell'elenco p, quindi eseguire il ciclo per eseguire nuovamente ps e dopo il secondo passaggio inviare SIGTERM, quindi SIGCONT. Se non ti interessano i bei finali, il secondo passaggio potrebbe essere SIGKILL, suppongo.


0

Se conosci il pid della cosa che vuoi uccidere, di solito puoi passare dall'ID sessione e tutto nella stessa sessione. Avrei ricontrollato, ma l'ho usato per gli script che avviano rsync in loop che voglio morire, e non ne inizio un altro (a causa del loop) come farebbe se uccidessi rsync.

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

Se non conosci il pid puoi comunque nidificare di più

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


0

In sh il comando jobs elencherà i processi in background. In alcuni casi potrebbe essere meglio interrompere prima il processo più recente, ad esempio quello precedente ha creato un socket condiviso. In questi casi, ordinare i PID in ordine inverso. A volte vuoi aspettare un momento prima che i lavori scrivano qualcosa sul disco o cose del genere prima che si fermino.

E non uccidere se non è necessario!

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

0

Uccidere il processo figlio nello script della shell:

Molte volte dobbiamo uccidere il processo figlio che viene impiccato o bloccato per qualche motivo. per esempio. Problema di connessione FTP.

Ci sono due approcci,

1) Creare un nuovo genitore separato per ogni bambino che controllerà e ucciderà il processo figlio una volta raggiunto il timeout.

Crea test.sh come segue,

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

e guarda i processi che hanno il nome come 'test' in altri terminali usando il seguente comando.

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

Lo script sopra creerà 4 nuovi processi figlio e i loro genitori. Ogni processo figlio verrà eseguito per 10 secondi. Ma una volta scaduto il tempo di 5 secondi, i rispettivi processi genitore uccideranno quei bambini. Quindi il bambino non sarà in grado di completare l'esecuzione (10 sec). Gioca attorno a quei tempi (interruttore 10 e 5) per vedere un altro comportamento. In tal caso, il bambino termina l'esecuzione in 5 secondi prima che raggiunga il timeout di 10 secondi.

2) Consentire al genitore corrente di monitorare e terminare il processo figlio una volta raggiunto il timeout. Questo non creerà un genitore separato per monitorare ogni bambino. Inoltre puoi gestire correttamente tutti i processi figlio all'interno dello stesso genitore.

Crea test.sh come segue,

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

e guarda i processi che hanno il nome come 'test' in altri terminali usando il seguente comando.

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

Lo script sopra creerà 4 nuovi processi figlio. Stiamo memorizzando i pid di tutto il processo figlio e li eseguiamo in loop per verificare se hanno terminato la loro esecuzione o se sono ancora in esecuzione. Il processo figlio verrà eseguito fino all'ora CMD_TIME. Ma se CNT_TIME_OUT raggiunge il timeout, tutti i bambini verranno uccisi dal processo padre. Puoi cambiare i tempi e giocare con lo script per vedere il comportamento. Uno svantaggio di questo approccio è l'uso dell'ID di gruppo per uccidere tutto l'albero figlio. Ma il processo genitore stesso appartiene allo stesso gruppo, quindi verrà anche ucciso.

Potrebbe essere necessario assegnare un altro ID gruppo al processo genitore se non si desidera che il genitore venga ucciso.

Maggiori dettagli possono essere trovati qui,

Uccidere il processo figlio nello script della shell


0

Questo script funziona anche:

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

So che è vecchio, ma questa è la soluzione migliore che ho trovato:

killtree() { 
    for p in $(pstree -p $1 | grep -o "([[:digit:]]*)" |grep -o "[[:digit:]]*" | tac);do
        echo Terminating: $p 
        kill $p
    done
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.