Qual é a melhor maneira de enviar um sinal para todos os membros de um grupo de processos?


422

Eu quero matar uma árvore de processo inteira. Qual é a melhor maneira de fazer isso usando qualquer linguagem de script comum? Estou procurando uma solução simples.


3
Os zumbis devem desaparecer quando o ceifador do sistema for executado. Admito que vi sistemas onde os zumbis permanecem, mas isso é atípico.
Brian Knoblauch

7
Às vezes, esses zumbis remanescentes são responsáveis ​​por alguma atividade assustadora.
precisa

Use um dos comandos chronosou herodes.
Michael Le Barbier Grünewald

8
Você pode
usar o seguinte comando

@ MichaelLeBarbierGrünewald Você poderia, por favor, criar um link para esses programas?
Styrofoam fly

Respostas:


305

Você não diz se a árvore que deseja matar é um único grupo de processos. (Geralmente, esse é o caso se a árvore for o resultado da bifurcação a partir de uma inicialização do servidor ou de uma linha de comando do shell.) Você pode descobrir grupos de processos usando o GNU ps da seguinte maneira:

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

Se você deseja matar um grupo de processos, basta usar o kill(1)comando, mas em vez de atribuir um número de processo, negue o número do grupo. Por exemplo, para matar todos os processos do grupo 5112, use kill -TERM -- -5112.


3
kill -74313 -bash: kill: 74313: especificação de sinal inválida Se eu adicionar o kill -15 -GPID, ele funcionou perfeitamente.
Adam Peck

51
Como de costume com quase qualquer comando, se você quiser um argumento normal que começa com um - para não ser interpretado como um switch, precedê-lo com -: matança - -GPID
ysth

9
pgreppode oferecer uma maneira mais fácil de encontrar o ID do grupo de processos. Por exemplo, para matar o grupo de processos do my-script.sh, execute kill -TERM -$(pgrep -o my-script.sh).
Josh Kelley

9
Melhor olhar para stackoverflow.com/questions/392022/… é de longe uma solução mais elegante e, se você precisar listar os pids das crianças, use:ps -o pid --no-headers --ppid $PARENT_PID
Szymon Jeé

4
E se você modificar um pouco o formato e classificar, poderá ver todos os processos bem agrupados e começando com (potencialmente) o pai do grupo em cada grupo:ps x -o "%r %p %y %x %c" | sort -nk1,2
haridsv

199

Mate todos os processos pertencentes à mesma árvore de processos usando o ID do grupo de processos ( PGID)

  • kill -- -$PGID     Use sinal padrão ( TERM= 15)
  • kill -9 -$PGID     Use o sinal KILL(9)

Você pode recuperar a PGIDpartir de qualquer Process-ID ( PID) da mesma árvore de processo

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

Agradecimentos especiais ao tanager e Speakus pelas contribuições nos $PIDespaços restantes e compatibilidade com OSX.

Explicação

  • kill -9 -"$PGID"=> Envie o sinal 9 ( KILL) para todos os filhos e netos ...
  • PGID=$(ps opgid= "$PID")=> Recupere o ID do grupo de processos de qualquer ID de processo da árvore, não apenas o ID dos pais do processo . Uma variação de ps opgid= $PIDé ps -o pgid --no-headers $PIDonde pgidpode ser substituída por pgrp.
    Mas:
    • psinsere espaços à esquerda com PIDmenos de cinco dígitos e alinhados à direita, conforme observado pelo tanager . Você pode usar:
      PGID=$(ps opgid= "$PID" | tr -d ' ')
    • psdo OSX sempre imprime o cabeçalho, portanto, o Speakus propõe:
      PGID="$( ps -o pgid "$PID" | grep [0-9] | tr -d ' ' )"
  • grep -o [0-9]* imprime apenas dígitos sucessivos (não imprime espaços ou cabeçalhos alfabéticos).

Linhas de comando adicionais

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)

Limitação

  • Conforme observado por davide e Hubert Kario , quando killé invocado por um processo pertencente à mesma árvore, killcorre o risco de se matar antes de terminar a matança inteira.
  • Portanto, certifique-se de executar o comando usando um processo com um ID de grupo de processos diferente .

Longa história

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

Execute a árvore de processos em segundo plano 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

O comando pkill -P $PIDnão mata o neto:

> 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

O comando kill -- -$PGIDmata todos os processos, incluindo o neto.

> 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

Conclusão

Percebo neste exemplo PIDe PGIDsão iguais ( 28957).
É por isso que originalmente pensei que kill -- -$PIDera suficiente. Mas, no caso em que o processo é gerado em um IDMakefile do processo, é diferente do ID do grupo .

Eu acho que kill -- -$(ps -o pgid= $PID | grep -o [0-9]*)é o melhor truque simples para matar uma árvore de processo inteira quando chamada de uma ID de grupo diferente (outra árvore de processo).


1
Olá @davide. Boa pergunta. Eu acho que killdeve sempre enviar o sinal para toda a árvore antes de receber seu próprio sinal. Mas, em algumas circunstâncias / implementações específicas, killpode enviar para si o sinal, ser interrompido e, em seguida, receber seu próprio sinal. No entanto, o risco deve ser mínimo o suficiente e pode ser ignorado na maioria dos casos, porque outros erros devem ocorrer antes deste. Esse risco pode ser ignorado no seu caso? Além disso, outras respostas têm esse bug comum ( killparte da árvore do processo sendo morta). Espero que esta ajuda .. Cheers;)
olibre 13/11

3
Isso só funciona se os subcomandos em si não se tornarem líderes de grupo. Mesmo ferramentas simples como manessas fazem isso. Por outro lado, se você quiser matar o processo gandchild do processo filho, kill -- -$pidnão funcionará. Portanto, não é uma solução genérica.
quer

1
O exemplo é o "filho" tentando matar seus filhos (então netos do comando iniciado pelo usuário). IOW, tente eliminar a hierarquia do processo em segundo plano child.sh.
precisa

1
> Matar -sair - "$ PGID" # mesmo sinal que [CRTL + C] do teclado ---- PARADO deve ser substituído para INT para ser verdade
Maxim Kholyavkin

1
para opção OSX - nenhum cabeçalho não é suportado, portanto, o código deve ser atualizado para: PGID = "$ (ps -o pgid" $ PID "| grep [0-9] | tr-d '')"
Maxim Kholyavkin

166
pkill -TERM -P 27888

Isso matará todos os processos que têm o ID do processo pai 27888.

Ou mais robusto:

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

que planejam matar 33 segundos depois e pedem educadamente que os processos sejam encerrados.

Veja esta resposta para encerrar todos os descendentes.


19
No meu teste rápido, o pgrep relatou apenas os filhos imediatos, portanto, isso pode não matar toda a hierarquia.
precisa

10
Concordo com @haridsv: pkill -Penvia o sinal apenas para o filho => o neto não recebe o sinal => Portanto, escrevi outra resposta para explicar isso. Cheers ;-)
olibre 28/02

1
Em um script bash, para matar seus próprios filhos, use pkill -TERM -P ${$}.
François Beausoleil

3
@Onlyjob, não é perigoso dormir e depois matar? Os IDs do processo podem ter sido reutilizados pelo sistema operacional nesse meio tempo: você pode estar matando processos que não são mais seus filhos. Eu suspeito que a chamada para pkill teria que ser feita novamente para garantir isso.
François Beausoleil

2
@Onlyjob FYI, eu sou capaz de gerar 32768 processos, de thread único, com acesso leve de E / S em menos de 19 segundos na minha máquina:$ time for i in {1..32768}; do ( echo $BASHPID >> pids ); done real 0m18.860s
cychoi

102

Para matar uma árvore de processo recursivamente, use 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
Os --argumentos para psnão funcionar no OS X. Para fazê-lo funcionar, substitua o pscomando por: ps ax -o "pid= ppid=" | grep -E "${_regex}" | sed -E "s/${_regex}/\1/gwhere _regexé definido antes do forloop:local _regex="[ ]*([0-9]+)[ ]+${_pid}"
artur

5
Processos interrompidos não são eliminados com o SIGTERM. Veja minha resposta
x-yuri

1
! -1 usos #! / Bin / bash em vez de # / usr / bin / env bash (ou melhor ainda POSIX apenas construções e / bin / sh)!
Boa Pessoa

Seria suficiente enviar um SIGKILL no lugar do SIGTERM para garantir que killtree()funcione de maneira confiável?
Davide 30/10

3
se psnão suporta --ppid, pode-se usar em pgrep -P ${_pid}vez disso
Hubert Kario

16

O comando rkill dopacote pslist envia o sinal fornecido (ouSIGTERMpor padrão) para o processo especificado e todos os seus descendentes:

rkill [-SIG] pid/name...

11

A resposta de Brad é o que eu recomendaria também, exceto que você pode eliminar awkcompletamente se usar a --ppidopção ps.

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

Isso não funciona para mim, a menos que eu retire o -ax, por algum motivo (Centos5). Caso contrário, isso é ótimo!
Xitrium

9

se você souber passar o pid do processo pai, aqui está um script de shell que deve funcionar:

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

Algumas versões do ps emitem um aviso se você usar "-ax" em vez de "ax". Assim: para filho em $ (ps -o pid, ppid ax | \ awk "{if (\ $ 2 == $ pid) {print \ $ 1}}")
Cory R. King

9

Eu uso uma versão um pouco modificada de um método descrito aqui: https://stackoverflow.com/a/5311362/563175

Então parece que:

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

onde 24901 é o PID dos pais.

Parece muito feio, mas funciona perfeitamente.


1
Simplificar com grep, em vez de sed ...pstree -p 24901 | grep -oP '(?<=\()[0-9]+(?=\))'
anishsane

2
você deve adicionar -la pstree, assim que as linhas longas não se truncado; também pode ser mais simples de ler kill `pstree -l -p 24901 |grep "([[:digit:]]*)" -o |tr -d '()'`(não é necessário converter \npara o espaço, pois funcionará bem), thx!
Poder de Aquário

9

Versão modificada da resposta de zhigang:

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

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

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

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

você pode wait $pidapenas em processos que você começou, nem todos os precessa, assim que esta não é uma solução genérica
Hubert Kario

@Hubert Kario Nesse caso, a espera sairá com um status diferente de zero e continuará executando o script. Estou errado? Mas waitsuprimirá a Terminatedmensagem se for uma criança.
x-yuri

9

Como não posso comentar (não há reputação suficiente), sou forçado a adicionar uma nova resposta , mesmo que essa não seja realmente uma resposta.

Há um pequeno problema com a resposta muito agradável e completa dada por @olibre em 28 de fevereiro. A saída de ps opgid= $PIDconterá espaços à esquerda para um PID menor que cinco dígitos porque psjustifica a coluna (alinhar os números corretamente). Dentro de toda a linha de comando, isso resulta em um sinal negativo, seguido por espaço (s), seguido pelo grupo PID. Solução simples é tubulação pspara tra espaços Remover:

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

Obrigado tanager. Vou corrigir a minha resposta;) Cheers
olibre

8

Para adicionar à resposta de Norman Ramsey, pode valer a pena olhar para setsid se você deseja criar um grupo de processos.
http://pubs.opengroup.org/onlinepubs/009695399/functions/setsid.html

A função setsid () deve criar uma nova sessão, se o processo de chamada não for um líder de grupo de processos. Após o retorno, o processo de chamada será o líder da sessão desta nova sessão, o líder do grupo de processos de um novo grupo de processos e não terá terminal de controle. O ID do grupo de processos do processo de chamada deve ser definido igual ao ID do processo de chamada. O processo de chamada deve ser o único processo no novo grupo de processos e o único processo na nova sessão.

Entendo que você pode criar um grupo a partir do processo inicial. Eu usei isso no php para poder matar uma árvore de processo inteira depois de iniciá-la.

Isso pode ser uma má ideia. Eu estaria interessado em comentários.


Na verdade, essa é uma ótima idéia e funciona muito bem. Estou usando-o nos casos em que posso colocar processos no mesmo grupo de processos (ou eles já estão no mesmo grupo).
sickill

7

Inspirado pelo comentário de ysth

kill -- -PGID

em vez de atribuir um número de processo, negue o número do grupo. Como de costume em quase qualquer comando, se você deseja que um argumento normal que comece com -a não seja interpretado como uma opção, anteceda-o com--


Opa, acabo de perceber que dei a mesma resposta que você => +1. Mas, além disso, eu explico como simplesmente obter PGIDa partir PID. O que você acha? Cheers
olibre

5

A seguinte função shell é semelhante a muitas das outras respostas, mas funciona no Linux e no BSD (OS X, etc) sem dependências externas, como 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
}

Eu acho que você tem a palavra extra "filho" no final da 2ª linha.
mato

@ mato - Isso não é extra. Limita o escopo $childdessa função para não perturbar outras variáveis ​​(não locais) com o mesmo nome e garantir que o valor da variável local seja limpo após o término da função.
Adam Katz

Oh, entendo agora, pensei que isso fazia parte da tarefa. Acho que devo reler (mais devagar) antes de escrever um comentário. :) Obrigado.
mato

Aliás, não seria melhor criar uma lista de filhos e começar a matar de cima (pai)? .. Assim, poderíamos evitar uma situação em que um pai recria um filho ou continua executando o próximo código que pode potencialmente alterar o comportamento pretendido.
mato

5

É super fácil fazer isso com python usando psutil . Basta instalar o psutil com o pip e você terá um conjunto completo de ferramentas de manipulação de processos:

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

5

Com base na resposta de zhigang, isso evita a auto-matança:

init_killtree() {
    local pid=$1 child

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

4

Se você deseja matar um processo pelo nome:

killall -9 -g someprocessname

ou

pgrep someprocessname | xargs pkill -9 -g

3

Esta é a minha versão de matar todos os processos filhos usando o script bash. Ele não usa recursão e depende do comando pgrep.

Usar

killtree.sh PID SIGNAL

Conteúdo de killtrees.sh

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

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

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

SIGNAL=$2

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

1
Isso falhará, se novos processos forem criados após a compilação da lista filho.
ceving

3

Aqui está uma variação da resposta do @ zhigang, que ocorre sem o AWK, contando apenas com as possibilidades de análise nativas do 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"
}

Parece funcionar bem em Macs e Linux. Em situações nas quais você não pode confiar em poder gerenciar grupos de processos - como ao escrever scripts para testar um software que deve ser construído em vários ambientes - essa técnica de caminhar em árvores é definitivamente útil.


1

Obrigado pela sua sabedoria, pessoal. Meu script estava deixando alguns processos filhos na saída e a dica de negação tornou as coisas mais fáceis. Eu escrevi essa função para ser usada em outros scripts, se necessário:

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

Felicidades.


1

Se você possui pstree e perl no seu sistema, pode tentar o seguinte:

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

1

Provavelmente é melhor matar os pais antes dos filhos; caso contrário, o pai pode gerar novos filhos novamente antes de ser morto. Estes vão sobreviver ao assassinato.

Minha versão do ps é diferente daquela acima; talvez muito velho, portanto o estranho grepping ...

Usar um script de shell em vez de uma função de shell tem muitas vantagens ...

No entanto, é basicamente ideia zhigangs


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

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

observe que o script @zhighang SIGSTOPs o processo pai e entrega o sinal ao processo parado, portanto, isso não deve (AFAIK) causar uma condição de corrida entre o processo de criação de filhos e a entrega do sinal. Sua versão, porém, tem uma corrida entre obter a lista de filhos e sinalizar a entrega aos pais.
quer

1

O seguinte foi testado no FreeBSD, Linux e MacOS X e depende apenas do pgrep e kill (as versões ps -o não funcionam no BSD). O primeiro argumento é o pai e pai dos quais os filhos devem ser encerrados. O segundo argumento é um booleano para determinar se o pid pai também deve ser finalizado.

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

Isso enviará o SIGTERM a qualquer processo filho / neto em um script de shell e, se o SIGTERM não for bem-sucedido, ele aguardará 10 segundos e enviará kill.


Resposta anterior:

O seguinte também funciona, mas mata o próprio shell no 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

Desenvolvo a solução de 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

Esta versão evitará matar sua ancestralidade - o que causa uma enxurrada de processos filhos nas soluções anteriores.

Os processos são interrompidos corretamente antes que a lista de filhos seja determinada, para que nenhum novo filho seja criado ou desapareça.

Após serem mortos, os trabalhos interrompidos precisam continuar para desaparecer do sistema.


1

Pergunta antiga, eu sei, mas todas as respostas parecem continuar chamando ps, das quais eu não gostei.

Essa solução baseada em awk não requer recursão e chama ps apenas uma vez.

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

Ou em uma única linha:

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

Basicamente, a idéia é que construamos uma matriz (a) de entradas pai: filho e, em seguida, percorra a matriz encontrando filhos para nossos pais correspondentes, adicionando-os à nossa lista de pais (p) à medida que avançamos.

Se você não deseja interromper o processo de nível superior, faça

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

pouco antes da linha system () a removeria do conjunto de interrupções.

Lembre-se de que existe uma condição de corrida aqui, mas isso é verdade (até onde posso ver) de todas as soluções. Ele faz o que eu precisava, porque o script para o qual eu precisava não cria muitas crianças de vida curta.

Um exercício para o leitor seria transformá-lo em um loop de 2 passagens: após a primeira passagem, envie SIGSTOP para todos os processos na lista p, depois faça um loop para executar ps novamente e após a segunda passagem envie SIGTERM e depois SIGCONT. Se você não se importa com finais agradáveis, o segundo passe pode ser SIGKILL, suponho.


0

Se você conhece o detalhe da coisa que deseja matar, geralmente pode ir do ID da sessão e de tudo na mesma sessão. Eu checava duas vezes, mas usei isso para scripts que iniciam o rsyncs em loops que eu quero morrer, e não inicio outro (por causa do loop) como faria se eu tivesse acabado de matar o rsync.

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

Se você não conhece o pid, ainda pode aninhar mais

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


0

Em sh, o comando jobs listará os processos em segundo plano. Em alguns casos, pode ser melhor eliminar primeiro o processo mais recente, por exemplo, o mais antigo criou um soquete compartilhado. Nesses casos, classifique os PIDs na ordem inversa. Às vezes, você deseja esperar um momento para que os trabalhos gravem algo no disco ou coisas assim antes que parem.

E não mate se não precisar!

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

0

Matando o processo filho no shell script:

Muitas vezes precisamos matar processos filhos que são pendurados ou bloqueados por algum motivo. por exemplo. Problema de conexão FTP.

Existem duas abordagens,

1) Criar novo pai separado para cada filho, que monitorará e interromperá o processo filho assim que o tempo limite for atingido.

Crie test.sh da seguinte maneira,

#!/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 observe os processos que estão tendo o nome como 'test' em outro terminal usando o seguinte comando.

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

O script acima criará 4 novos processos filhos e seus pais. Cada processo filho será executado por 10 segundos. Porém, quando o tempo limite de 5s atingir, seus respectivos processos pai matarão esses filhos. Portanto, o filho não poderá concluir a execução (10s). Brinque com esses horários (alterne 10 e 5) para ver outro comportamento. Nesse caso, o filho concluirá a execução em 5 segundos antes de atingir o tempo limite de 10 segundos.

2) Deixe o pai atual monitorar e interromper o processo filho quando o tempo limite for atingido. Isso não criará um pai separado para monitorar cada filho. Além disso, você pode gerenciar todos os processos filhos corretamente dentro do mesmo pai.

Crie test.sh da seguinte maneira,

#!/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 observe os processos que estão tendo o nome como 'test' em outro terminal usando o seguinte comando.

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

O script acima criará 4 novos processos filhos. Estamos armazenando pids de todos os processos filhos e fazendo um loop sobre eles para verificar se eles concluíram sua execução ou ainda estão em execução. O processo filho será executado até o horário CMD_TIME. Mas se o tempo limite de CNT_TIME_OUT atingir, todos os filhos serão mortos pelo processo pai. Você pode mudar o tempo e brincar com o script para ver o comportamento. Uma desvantagem dessa abordagem é que ela está usando a identificação de grupo para eliminar todas as árvores filhas. Mas o processo pai pertence ao mesmo grupo e, portanto, também é morto.

Pode ser necessário atribuir outra identificação de grupo ao processo pai, se você não quiser que o pai seja morto.

Mais detalhes podem ser encontrados aqui,

Matando processo filho no shell script


0

Este script também funciona:

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

Eu sei que é velho, mas essa é a melhor solução que eu encontrei:

killtree() { 
    for p in $(pstree -p $1 | grep -o "([[:digit:]]*)" |grep -o "[[:digit:]]*" | tac);do
        echo Terminating: $p 
        kill $p
    done
}
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.