Obtenga una lista elegante de procesos descendientes

23

Me gustaría obtener una lista de todos los procesos que descienden (por ejemplo, hijos, nietos, etc.) $pid. Esta es la forma más simple que se me ocurrió:

pstree -p $pid | tr "\n" " " |sed "s/[^0-9]/ /g" |sed "s/\s\s*/ /g"

¿Hay algún comando, o alguna forma más simple de obtener la lista completa de todos los procesos descendientes?

STenyaK
fuente
¿Hay alguna razón por la que los necesita todos en una línea? ¿Qué haces con esa salida? Tengo la sensación de que este es un problema xy, y está haciendo la pregunta equivocada.
jordanm
No me importa el formato siempre que esté limpio (es decir, no me importa lo '\n'delimitado frente a ' 'delimitado). El caso de uso práctico es: a) un script daemonizer que escribí por puro masoquismo (específicamente, la funcionalidad "stop" tiene que lidiar con cualquier árbol de procesos que generó el proceso demonizado); y b) una secuencia de comandos de tiempo de espera que matará a cualquiera que sea el tiempo de espera superado el proceso se gestiona a crear.
STenyaK
2
@STenyaK Sus casos de uso me hacen pensar que está buscando grupos de procesos y un argumento negativo para kill. Ver unix.stackexchange.com/questions/9480/… , unix.stackexchange.com/questions/50555/…
Gilles 'SO- deja de ser malvado'
@Gilles usando ps ax -opid,ppid,pgrp,cmdVeo que hay muchos procesos que comparten lo mismo pgrpque el subárbol exacto que quiero matar. (Además, no puedo ver el setpgrpprograma en ninguna parte de los paquetes estables de Debian: packages.debian.org/… )
STenyaK
1
Otro caso de uso: renice / ionice en un árbol de proceso completo que consume demasiados recursos, por ejemplo, una gran construcción paralela.
Cheetah

Respuestas:

15

Lo siguiente es algo más simple y tiene la ventaja adicional de ignorar los números en los nombres de los comandos:

pstree -p $pid | grep -o '([0-9]\+)' | grep -o '[0-9]\+'

O con Perl:

pstree -p $pid | perl -ne 'print "$1\n" while /\((\d+)\)/g'

Estamos buscando números entre paréntesis para no dar, por ejemplo, 2 como un proceso secundario cuando nos encontramos gif2png(3012). Pero si el nombre del comando contiene un número entre paréntesis, todas las apuestas están desactivadas. Hasta ahora solo puede llevarte el procesamiento de texto.

Así que también creo que los grupos de procesos son el camino a seguir. Si desea ejecutar un proceso en su propio grupo de procesos, puede utilizar la herramienta 'pgrphack' del paquete Debian 'daemontools':

pgrphack my_command args

O podrías volver a Perl:

perl -e 'setpgid or die; exec { $ARGV[0] } @ARGV;' my_command args

La única advertencia aquí es que los grupos de procesos no anidan, por lo que si algún proceso está creando sus propios grupos de procesos, sus subprocesos ya no estarán en el grupo que usted creó.

Jander
fuente
Los procesos secundarios son arbitrarios y pueden usar o no grupos de procesos (no puedo asumir nada). Sin embargo, su respuesta es lo más parecido a lo que parece ser posible en Linux, por lo que lo aceptaré. Gracias.
STenyaK
¡Esto fue muy útil!
Michal Gallovic
Las tuberías pstree también incluirán los identificadores de subproceso, es decir, los ID de los subprocesos que ha iniciado un $ pid.
maxschlepzig
Puede usar grep individual:pstree -lp | grep -Po "(?<=\()\d+(?=\))"
puchu
7
descendent_pids() {
    pids=$(pgrep -P $1)
    echo $pids
    for pid in $pids; do
        descendent_pids $pid
    done
}
Russell Davis
fuente
Sería sólo vale la pena señalar que esto va a funcionar en conchas modernas ( bash, zsh, fish, e incluso ksh 99), pero puede que no funcione en cáscaras de más edad, por ejemploksh 88
Grochmal
@grochmal, vea mi respuesta a continuación para obtener una solución transversal que funciona en ksh-88.
maxschlepzig
1

La versión más corta que he encontrado que también trata correctamente con comandos como pop3d:

pstree -p $pid | perl -ne 's/\((\d+)\)/print " $1"/ge'

Se ocupa erróneamente si tiene comandos que tienen nombres extraños como: my(23)prog.

Ole Tange
fuente
1
Esto no funciona para los comandos que ejecutan algún hilo (porque pstree también imprime esas ID).
maxschlepzig
@maxschlepzig Noté ese mismo problema con el ffmpeguso de hilos. Sin embargo, a partir de observaciones rápidas, parece que los hilos se dan con su nombre dentro de llaves, { }.
Gypsy Spellweaver
1

También está la cuestión de la corrección. Analizar ingenuamente la salida de pstreees problemático por varias razones:

  • pstree muestra los PID y los identificadores de los hilos (los nombres se muestran entre llaves)
  • un nombre de comando puede contener llaves, números entre paréntesis que hacen imposible el análisis confiable

Si tiene Python y el psutilpaquete instalado, puede usar este fragmento para enumerar todos los procesos descendientes:

pid=2235; python3 -c "import psutil
for c in psutil.Process($pid).children(True):
  print(c.pid)"

(El paquete psutil se instala, por ejemplo, como una dependencia del tracercomando que está disponible en Fedora / CentOS).

Alternativamente, puede hacer un recorrido transversal del árbol de procesos en un shell de bourne:

ps=2235; while [ "$ps" ]; do echo $ps; ps=$(echo $ps | xargs -n1 pgrep -P); \
  done | tail -n +2 | tr " " "\n"

Para calcular el cierre transitivo de un pid, se puede omitir la parte de cola.

Tenga en cuenta que lo anterior no usa recursividad y también se ejecuta en ksh-88.

En Linux, uno puede eliminar la pgrepllamada y en su lugar leer la información de /proc:

ps=2235; while [ "$ps" ]; do echo $ps ; \
  ps=$(for p in $ps; do cat /proc/$p/task/$p/children; done); done \
  | tr " " "\n"' | tail -n +2

Esto es más eficiente porque guardamos un fork / exec para cada PID y hacemos un pgreptrabajo adicional en cada llamada.

maxschlepzig
fuente
1

Esta versión de Linux solo necesita / proc y ps. Está adaptado de la última parte de la excelente respuesta de @ maxschlepzig . Esta versión lee / proc directamente desde el shell en lugar de generar un subproceso en un bucle. Es un poco más rápido y posiblemente un poco más elegante, como lo solicita este título de hilo.

#!/bin/dash

# Print all descendant pids of process pid $1
# adapted from /unix//a/339071

ps=${1:-1}
while [ "$ps" ]; do
  echo $ps
  unset ps1 ps2
  for p in $ps; do
    read ps2 < /proc/$p/task/$p/children 2>/dev/null
    ps1="$ps1 $ps2"
  done
  ps=$ps1
done | tr " " "\n" | tail -n +2
stepse
fuente
0

En cada uno de sus dos casos de uso (aparentemente muy artificiales), ¿por qué quiere matar los subprocesos de algunos procesos desafortunados? ¿Cómo sabes mejor que un proceso cuando sus hijos deberían vivir o morir? Esto me parece un mal diseño; un proceso debe limpiarse después de sí mismo.

Si realmente sabe mejor, entonces debería estar bifurcando estos subprocesos, y el 'proceso demonizado' es aparentemente demasiado tonto para confiar en él fork(2).

Debe evitar mantener listas de procesos secundarios o avanzar por el árbol de procesos, por ejemplo, colocando los procesos secundarios en un grupo de procesos separado como lo sugiere @Gilles.

En cualquier caso, sospecho que su proceso demonizado sería mejor creando un grupo de subprocesos de trabajo (que necesariamente muere junto con su proceso de contención) que un árbol profundo de sub-sub-sub-subprocesos, que algo en algún lugar luego tiene que limpiar .

AnotherSmellyGeek
fuente
2
Ambos casos de uso se utilizan en un entorno continuo de integración / prueba, por lo que tienen que lidiar con la posibilidad de que exista un error en los procesos secundarios. Este error puede manifestarse como la incapacidad de apagarse adecuadamente a sí mismos o a sus hijos, por lo que necesito una manera de asegurarme de poder cerrarlos a todos en el peor de los casos.
STenyaK
1
En ese caso, estoy con @Gilles y @Jander; Los grupos de procesos son la mejor manera.
AnotherSmellyGeek
0

Aquí hay un script de envoltorio pgrep que le permite usar pgrep y obtener todos los descendientes al mismo tiempo.

~/bin/pgrep_wrapper:

#!/bin/bash

# the delimiter argument must be the first arg, otherwise it is ignored
delim=$'\n'
if [ "$1" == "-d" ]; then
    delim=$2
    shift 2
fi

pids=
newpids=$(pgrep "$@")
status=$?
if [ $status -ne 0 ]; then
    exit $status
fi

while [ "$pids" != "$newpids" ]; do
    pids=$newpids
    newpids=$( { echo "$pids"; pgrep -P "$(echo -n "$pids" | tr -cs '[:digit:]' ',')"; } | sort -u )
done
if [ "$delim" != $'\n' ]; then
    first=1
    for pid in $pids; do
        if [ $first -ne 1 ]; then
            echo -n "$delim"
        else
            first=0
        fi  
        echo -n "$pid"
    done
else
    echo "$pids"
fi

Invoque de la misma manera que invocaría pgrep normal, como pgrep_recursive -U $USER javapara encontrar todos los procesos y subprocesos Java del usuario actual.

zeroimpl
fuente
1
Como esto es bash, tengo la sensación de que el código utilizado para unir los PID con el delimitador podría reemplazarse con la configuración IFSy el uso de matrices ( "${array[*]}").
muru