¿Cuál es la mejor manera de enviar una señal a todos los miembros de un grupo de procesos?


422

Quiero matar todo un árbol de procesos. ¿Cuál es la mejor manera de hacer esto usando cualquier lenguaje de script común? Estoy buscando una solución simple.


3
Sin embargo, los zombis deberían desaparecer cuando se ejecuta el segador del sistema. Admito que he visto sistemas donde los zombis permanecen, pero eso es atípico.
Brian Knoblauch

77
A veces, esos zombis persistentes son responsables de alguna actividad aterradora.
Usuario1

Use uno de los comandos chronoso herodes.
Michael Le Barbier Grünewald

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

@ MichaelLeBarbierGrünewald ¿Podría vincular a esos programas?
espuma de poliestireno volar

Respuestas:


305

No dice si el árbol que desea matar es un solo grupo de procesos. (Este suele ser el caso si el árbol es el resultado de una bifurcación desde el inicio de un servidor o una línea de comando de shell). Puede descubrir grupos de procesos utilizando GNU ps de la siguiente manera:

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

Si es un grupo de proceso que desea eliminar, simplemente use el kill(1)comando pero en lugar de darle un número de proceso, dele la negación del número de grupo. Por ejemplo, para eliminar todos los procesos del grupo 5112, use kill -TERM -- -5112.


3
kill -74313 -bash: kill: 74313: especificación de señal no válida Si agrego kill -15 -GPID funcionó perfectamente.
Adam Peck

51
Como es habitual con casi cualquier comando, si desea que un argumento normal que comience con un - no se interprete como un interruptor, preceda con -: kill - -GPID
ysth

99
pgreppuede ofrecer una manera más fácil de encontrar la ID del grupo de procesos. Por ejemplo, para matar el grupo de procesos my-script.sh, ejecute kill -TERM -$(pgrep -o my-script.sh).
Josh Kelley el

99
Mire mejor stackoverflow.com/questions/392022/… es, con mucho, una solución más elegante y si necesita enumerar las imágenes de los niños, use:ps -o pid --no-headers --ppid $PARENT_PID
Szymon Jeż

44
Y si modifica el formato un poco y ordena, verá todos los procesos bien agrupados y comenzando con (potencialmente) el grupo principal en cada grupo:ps x -o "%r %p %y %x %c" | sort -nk1,2
haridsv

199

Elimine todos los procesos que pertenecen al mismo árbol de procesos utilizando el ID del grupo de procesos ( PGID)

  • kill -- -$PGID     Usar señal predeterminada ( TERM= 15)
  • kill -9 -$PGID     Usa la señal KILL(9)

Puede recuperar el PGIDdesde cualquier ID de proceso ( PID) del mismo árbol de procesos

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

Un agradecimiento especial a tanager y Speakus por sus contribuciones en $PIDlos espacios restantes y la compatibilidad con OSX.

Explicación

  • kill -9 -"$PGID"=> Enviar señal 9 ( KILL) a todos los hijos y nietos ...
  • PGID=$(ps opgid= "$PID")=> Recupere la ID de grupo de proceso de cualquier ID de proceso del árbol, no solo la ID de padre de proceso . Una variación de ps opgid= $PIDes ps -o pgid --no-headers $PIDdonde pgidse puede reemplazar por pgrp.
    Pero:
    • psinserta espacios iniciales cuando PIDtiene menos de cinco dígitos y se alinea a la derecha como lo notó la tangara . Puedes usar:
      PGID=$(ps opgid= "$PID" | tr -d ' ')
    • psdesde OSX siempre imprime el encabezado, por lo tanto, Speakus propone:
      PGID="$( ps -o pgid "$PID" | grep [0-9] | tr -d ' ' )"
  • grep -o [0-9]* imprime solo dígitos sucesivos (no imprime espacios ni encabezados alfabéticos).

Otras líneas de 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)

Limitación

  • Como notaron Davide y Hubert Kario , cuando killes invocado por un proceso que pertenece al mismo árbol, killcorre el riesgo de suicidarse antes de terminar con la destrucción del árbol completo.
  • Por lo tanto, asegúrese de ejecutar el comando utilizando un proceso que tenga una ID de grupo de proceso diferente .

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

Ejecute el árbol de procesos en 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

El comando pkill -P $PIDno mata al nieto:

> 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

El comando kill -- -$PGIDmata todos los procesos, incluido el nieto.

> 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

Conclusión

Noto en este ejemplo PIDy PGIDson iguales ( 28957).
Es por eso que originalmente pensé que kill -- -$PIDera suficiente. Pero en el caso de que el proceso se desovar dentro de un Makefileel ID de proceso es diferente del ID de grupo .

Creo que kill -- -$(ps -o pgid= $PID | grep -o [0-9]*)es el mejor truco simple para matar un árbol de proceso completo cuando se llama desde una ID de grupo diferente (otro árbol de proceso).


1
Hola @davide Buena pregunta. Creo killque siempre debería enviar la señal a todo el árbol antes de recibir su propia señal. Pero en algunas circunstancias / implementaciones específicas, killpuede enviarse la señal a sí mismo, ser interrumpido y luego recibir su propia señal. Sin embargo, el riesgo debe ser lo suficientemente mínimo, y puede ignorarse en la mayoría de los casos porque deberían ocurrir otros errores antes de este. ¿Se puede ignorar este riesgo en su caso? Además, otras respuestas tienen este error común ( killparte del árbol de proceso que se está eliminando). Espero que esta ayuda .. Saludos;)
olibre

3
Esto solo funciona si los subcomandos no se convierten en líderes de grupo. Incluso herramientas tan simples como manhacer eso. Por otro lado, si desea eliminar el proceso gandchild del proceso hijo, kill -- -$pidno funcionará. Por lo tanto, no es una solución genérica.
Hubert Kario el

1
El ejemplo es el "niño" que intenta matar a sus hijos (por lo que los nietos del comando iniciado por el usuario). IOW, intenta eliminar la jerarquía del proceso en segundo plano child.sh.
Hubert Kario el

1
> kill -QUIT - "$ PGID" # misma señal que [CRTL + C] desde el teclado ---- QUIT debe ser reemplazado por INT para ser verdad
Maxim Kholyavkin

1
para la opción OSX --no-headers no es compatible, por lo que el código debe actualizarse a: PGID = "$ (ps -o pgid" $ PID "| grep [0-9] | tr -d '')"
Maxim Kholyavkin

166
pkill -TERM -P 27888

Esto eliminará todos los procesos que tengan el ID de proceso padre 27888.

O más robusto:

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

que programan la muerte 33 segundos después y cortésmente solicitan que finalicen los procesos.

Vea esta respuesta para terminar con todos los descendientes.


19
En mi prueba rápida, pgrep solo informó a los hijos inmediatos, por lo que esto puede no matar a toda la jerarquía.
haridsv

10
Estoy de acuerdo con @haridsv: pkill -Penvía la señal solo al niño => el nieto no recibe la señal => Por lo tanto, he escrito otra respuesta para explicar eso. Saludos ;-)
olibre

1
Desde un script bash, para matar a tus propios hijos, usa pkill -TERM -P ${$}.
François Beausoleil

3
@Onlyjob, ¿no es peligroso dormir y luego matar? Mientras tanto, el sistema operativo puede haber reutilizado los ID de proceso: es posible que esté eliminando procesos que ya no son sus hijos. Sospecho que la llamada a pkill tendría que hacerse de nuevo para asegurarse de esto.
François Beausoleil

2
@Onlyjob FYI, puedo generar 32768 procesos, de un solo subproceso, con acceso de E / S ligero en menos de 19 segundos en mi máquina:$ time for i in {1..32768}; do ( echo $BASHPID >> pids ); done real 0m18.860s
cychoi

102

Para matar un árbol de proceso de forma recursiva, 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
Los --argumentos para psno funcionar en OS X. Para hacerlo funcionar, reemplace el pscomando por: ps ax -o "pid= ppid=" | grep -E "${_regex}" | sed -E "s/${_regex}/\1/gdonde _regexse define antes del forbucle:local _regex="[ ]*([0-9]+)[ ]+${_pid}"
artur

55
Los procesos detenidos no se matan con SIGTERM. Ver mi respuesta
x-yuri

1
-1 usa #! / Bin / bash en lugar de #! / Usr / bin / env bash (o mejor aún, POSIX solo construye y / bin / sh)
Buena persona

¿Sería suficiente enviar un SIGKILL en lugar de SIGTERM para garantizar que killtree()funcione de manera confiable?
davide

3
si psno es compatible --ppid, se puede usar pgrep -P ${_pid}en su lugar
Hubert Kario


11

La respuesta de Brad es lo que yo también recomendaría, excepto que puede eliminarlo por awkcompleto si usa la --ppidopción ps.

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

Esto no funciona para mí a menos que elimine -ax, por alguna razón (Centos5). De lo contrario, esto es genial!
xitrium

9

si sabes pasar el pid del proceso padre, aquí hay un script de shell que debería 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

Algunas versiones de ps arrojarán una advertencia si usa "-ax" en lugar de "ax". Por lo tanto: para el niño en $ (ps -o pid, ppid ax | \ awk "{if (\ $ 2 == $ pid) {print \ $ 1}}")
Cory R. King

9

Utilizo una versión un poco modificada de un método descrito aquí: https://stackoverflow.com/a/5311362/563175

Entonces se ve así:

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

donde 24901 es el PID de los padres.

Se ve bastante feo pero hace su trabajo perfectamente.


1
Simplificando con grep, en lugar de sed ...pstree -p 24901 | grep -oP '(?<=\()[0-9]+(?=\))'
anishsane

2
debe agregar -la pstree, para que las líneas largas no se trunqueen se puede hacer más fácil de leer también con kill `pstree -l -p 24901 |grep "([[:digit:]]*)" -o |tr -d '()'`(no es necesario convertir \nal espacio ya que funcionará bien), gracias!
Acuario Power

9

Versión modificada de la respuesta 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

wait $pidsolo puede en procesos que ha comenzado, no en todos los precesos, por lo que esta no es una solución genérica
Hubert Kario

@Hubert Kario En ese caso, la espera simplemente saldrá con un estado distinto de cero y continuará ejecutando el script. ¿Me equivoco? Pero waitsuprimirá el Terminatedmensaje si es un niño.
x-yuri

9

No puedo comentar (no hay suficiente reputación), por lo que me veo obligado a agregar una nueva respuesta , aunque esta no sea realmente una respuesta.

Hay un pequeño problema con la respuesta por lo demás muy agradable y exhaustiva dada por @olibre el 28 de febrero. La salida de ps opgid= $PIDcontendrá espacios iniciales para un PID de menos de cinco dígitos porque psestá justificando la columna (alinee los números). Dentro de la línea de comando completa, esto da como resultado un signo negativo, seguido de espacio (s), seguido del PID del grupo. Una solución simple es a la tubería psa trlos espacios eliminar los siguientes:

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

Gracias tanager.
Arreglaré

8

Para agregar a la respuesta de Norman Ramsey, puede valer la pena mirar setsid si desea crear un grupo de procesos.
http://pubs.opengroup.org/onlinepubs/009695399/functions/setsid.html

La función setsid () creará una nueva sesión, si el proceso de llamada no es un líder de grupo de proceso. Al regresar, el proceso de llamada será el líder de la sesión de esta nueva sesión, será el líder del grupo de proceso de un nuevo grupo de proceso y no tendrá un terminal de control. La ID del grupo de proceso del proceso de llamada se establecerá igual a la ID de proceso del proceso de llamada. El proceso de llamada será el único proceso en el nuevo grupo de procesos y el único proceso en la nueva sesión.

Lo que quiero decir es que puedes crear un grupo desde el proceso de inicio. Usé esto en php para poder matar un árbol de proceso completo después de iniciarlo.

Esta puede ser una mala idea. Me interesarían los comentarios.


En realidad, esta es una gran idea y funciona muy bien. Lo estoy usando en casos en los que puedo poner procesos en el mismo grupo de procesos (o ya están en el mismo grupo).
sickill

7

Inspirado por el comentario de ysth

kill -- -PGID

en lugar de darle un número de proceso, dele la negación del número de grupo. Como es habitual con casi cualquier comando, si desea que un argumento normal que comienza con -a no se interprete como un interruptor, preceda con--


Vaya, me acabo de dar cuenta de que he dado la misma respuesta que tú => +1. Pero además de explicar cómo conseguir simplemente PGIDa partir PID. ¿Qué piensas? Saludos
olibre 01 de

5

La siguiente función de shell es similar a muchas de las otras respuestas, pero funciona tanto en Linux como en BSD (OS X, etc.) sin dependencias 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
}

Creo que tiene la palabra adicional "niño" al final de la segunda línea.
mato

@mato - Eso no es extra. Limita el alcance de $childesa función para no perturbar otras variables (no locales) con el mismo nombre y garantizar que el valor de la variable local se limpie después de que finalice la función.
Adam Katz

Oh, ya veo, pensé que era parte de la tarea. Supongo que debería volver a leer (más lento) antes de escribir un comentario. :) Gracias.
mato

Por cierto, ¿no sería mejor crear una lista de niños y luego comenzar a matar desde arriba (padre)? .. Por lo tanto, podríamos evitar una situación en la que un padre recrea a un niño o continúa ejecutando el siguiente código que podría alterar el comportamiento deseado.
mato

5

Es muy fácil hacer esto con python usando psutil . Simplemente instale psutil con pip y luego tendrá un conjunto completo de herramientas de manipulación de procesos:

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

5

Basado en la respuesta de zhigang, esto evita el auto-asesinato:

init_killtree() {
    local pid=$1 child

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

4

Si quieres matar un proceso por nombre:

killall -9 -g someprocessname

o

pgrep someprocessname | xargs pkill -9 -g

3

Esta es mi versión de matar todos los procesos secundarios usando el script bash. No usa recursividad y depende del comando pgrep.

Utilizar

killtree.sh PID SIGNAL

Contenido 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
Esto fallará si se crean nuevos procesos después de que se haya creado la lista secundaria.
ceving

3

Aquí hay una variación de la respuesta de @ zhigang que funciona sin AWK, confiando solo en las posibilidades de análisis nativo de Bash:

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

Parece funcionar bien tanto en Mac como en Linux. En situaciones en las que no puede confiar en la capacidad de administrar grupos de procesos, como cuando escribe scripts para probar un software que debe construirse en múltiples entornos, esta técnica de caminar por los árboles es definitivamente útil.


1

Gracias por su sabiduría, amigos. Mi script dejaba algunos procesos secundarios al salir y el consejo de negación facilitó las cosas. Escribí esta función para usarla en otros scripts si es necesario:

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

Salud.


1

Si tiene pstree y perl en su sistema, puede intentar esto:

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

1

Probablemente sea mejor matar al padre antes que a los hijos; de lo contrario, es probable que el padre genere nuevos hijos antes de que él mismo se mate. Estos sobrevivirán a la matanza.

Mi versión de ps es diferente de la anterior; tal vez demasiado viejo, por lo tanto, el extraño grepping ...

Usar un script de shell en lugar de una función de shell tiene muchas ventajas ...

Sin embargo, es básicamente idea 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

tenga en cuenta que el script @zhighang SIGSTOPs el proceso padre y entrega la señal al proceso detenido, por lo que esto no debería (AFAIK) causar una condición de carrera entre el proceso de creación de niños y la entrega de la señal. Sin embargo, su versión tiene competencia entre obtener la lista de niños y enviar la señal a los padres.
Hubert Kario el

1

Lo siguiente ha sido probado en FreeBSD, Linux y MacOS X y solo depende de pgrep y kill (las versiones ps -o no funcionan bajo BSD). El primer argumento es el padre pid de que los hijos tienen que ser terminados. El segundo argumento es un valor booleano para determinar si el pid padre también debe terminarse.

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

Esto enviará SIGTERM a cualquier proceso hijo / nieto dentro de un script de shell y si SIGTERM no tiene éxito, esperará 10 segundos y luego enviará kill.


Respuesta anterior:

Lo siguiente también funciona, pero matará el propio shell en 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

Desarrollo aún más la solución de zhigang, xyuri y 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 versión evitará matar a sus ancestros, lo que provoca una avalancha de procesos secundarios en las soluciones anteriores.

Los procesos se detienen correctamente antes de determinar la lista secundaria, para que no se creen o desaparezcan nuevos elementos secundarios.

Después de ser asesinados, los trabajos detenidos deben continuar para desaparecer del sistema.


1

Antigua pregunta, lo sé, pero todas las respuestas parecen seguir llamando ps, lo que no me gustó.

Esta solución basada en awk no requiere recursividad y solo llama ps una 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)
}'

O en una sola línea:

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

Básicamente, la idea es que construyamos una matriz (a) de entradas padre: hijo, luego recorramos la matriz buscando hijos para nuestros padres coincidentes, agregándolos a nuestra lista de padres (p) a medida que avanzamos.

Si no quieres matar el proceso de nivel superior, entonces haz

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

justo antes de que la línea system () lo elimine del conjunto de eliminación.

Tenga en cuenta que hay una condición de carrera aquí, pero eso es cierto (hasta donde puedo ver) de todas las soluciones. Hace lo que necesitaba porque el script para el que lo necesitaba no crea muchos niños de corta duración.

Un ejercicio para el lector sería convertirlo en un ciclo de 2 pasadas: después de la primera pasada, envíe SIGSTOP a todos los procesos en la lista p, luego realice un ciclo para ejecutar ps nuevamente y después de la segunda pasada envíe SIGTERM, luego SIGCONT. Si no te interesan los finales agradables, supongo que el segundo pase podría ser SIGKILL.


0

Si conoce el pid de lo que desea matar, generalmente puede ir desde la identificación de la sesión y todo en la misma sesión. Verificaría dos veces, pero usé esto para scripts que inician rsyncs en bucles que quiero morir, y no iniciar otro (debido al bucle) como lo haría si acabara de matar a rsync.

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

Si no conoces el pid, aún puedes anidar más

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


0

En sh, el comando jobs enumerará los procesos en segundo plano. En algunos casos, podría ser mejor eliminar primero el proceso más nuevo, por ejemplo, el anterior creó un socket compartido. En esos casos, clasifique los PID en orden inverso. A veces quieres esperar un momento para que los trabajos escriban algo en el disco o cosas así antes de que se detengan.

¡Y no mates si no tienes que hacerlo!

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

0

Proceso secundario de matar en script de shell:

Muchas veces necesitamos matar procesos secundarios que se cuelgan o bloquean por alguna razón. p.ej. Problema de conexión FTP.

Hay dos enfoques,

1) Crear un nuevo padre separado para cada hijo que supervisará y eliminará el proceso hijo una vez que se agote el tiempo de espera.

Cree test.sh de la siguiente manera,

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

y observe los procesos que tienen nombre como 'prueba' en otro terminal usando el siguiente comando.

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

El script anterior creará 4 nuevos procesos secundarios y sus padres. Cada proceso secundario se ejecutará durante 10 segundos. Pero una vez que se agota el tiempo de espera de 5 segundos, sus respectivos procesos principales matarán a esos niños. Por lo tanto, el niño no podrá completar la ejecución (10 segundos). Juega alrededor de esos tiempos (interruptor 10 y 5) para ver otro comportamiento. En ese caso, el hijo finalizará la ejecución en 5 segundos antes de que alcance el tiempo de espera de 10 segundos.

2) Deje que el padre actual supervise y elimine el proceso hijo una vez que se agote el tiempo de espera. Esto no creará un padre separado para monitorear a cada niño. También puede administrar todos los procesos secundarios correctamente dentro del mismo padre.

Cree test.sh de la siguiente manera,

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

y observe los procesos que tienen nombre como 'prueba' en otro terminal usando el siguiente comando.

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

El script anterior creará 4 nuevos procesos secundarios. Estamos almacenando pids de todos los procesos secundarios y recorriéndolos para verificar si han terminado su ejecución o si aún se están ejecutando. El proceso hijo se ejecutará hasta el momento CMD_TIME. Pero si CNT_TIME_OUT alcanza el tiempo de espera, todos los niños serán asesinados por el proceso principal. Puede cambiar el tiempo y jugar con el script para ver el comportamiento. Un inconveniente de este enfoque es que está usando la identificación de grupo para matar todos los árboles secundarios. Pero el proceso padre en sí mismo pertenece al mismo grupo, por lo que también será asesinado.

Es posible que deba asignar otra identificación de grupo al proceso principal si no desea que se elimine el elemento principal.

Más detalles se pueden encontrar aquí,

Proceso secundario de matar en script de shell


0

Este script también 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

Sé que es viejo, pero esa es la mejor solución que encontré:

killtree() { 
    for p in $(pstree -p $1 | grep -o "([[:digit:]]*)" |grep -o "[[:digit:]]*" | tac);do
        echo Terminating: $p 
        kill $p
    done
}
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.