Obtenha elegantemente a lista de processos descendentes

23

Eu gostaria de obter uma lista de todos os processos que descendem (por exemplo, filhos, netos, etc.) $pid. Esta é a maneira mais simples de criar:

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

Existe algum comando ou alguma maneira mais simples de obter a lista completa de todos os processos descendentes?

STenyaK
fonte
Existe uma razão para você precisar de todos eles em uma linha? O que você está fazendo com essa saída? Sinto que este é um problema xy e você está fazendo a pergunta errada.
jordanm
Eu não me importo com o formato desde que seja limpo (ou seja, não me importo com '\n'delimitado x ' 'delimitado). O caso de uso prático é: a) um script daemonizer que escrevi com puro masoquismo (especificamente, a funcionalidade "stop" tem que lidar com qualquer árvore de processos que o processo daemonized gerou); e b) um script tempo limite que vai matar qualquer que seja a excedido processo conseguiu criar.
STenyaK
2
@STenyaK Seus casos de uso me fazem pensar que você está procurando por grupos de processos e argumentos negativos kill. Veja unix.stackexchange.com/questions/9480/... , unix.stackexchange.com/questions/50555/...
Gilles 'SO parada sendo maus'
@ Gilles usando ps ax -opid,ppid,pgrp,cmdvejo que existem muitos processos que compartilham o mesmo pgrpque a subárvore exata que quero matar. (Além disso, eu não posso ver o setpgrpprograma listado em qualquer lugar pacotes estável do Debian: packages.debian.org/... )
STenyaK
1
Outro caso de uso: renice / ionice em toda uma árvore de processos que consome muitos recursos, por exemplo, uma grande compilação paralela.
Cheetah

Respostas:

15

A seguir, é um pouco mais simples e tem a vantagem adicional de ignorar números nos nomes dos comandos:

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

Ou com Perl:

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

Estamos procurando números entre parênteses, para não dar, por exemplo, 2 como um processo filho quando nos deparamos gif2png(3012). Mas se o nome do comando contiver um número entre parênteses, todas as apostas serão desativadas. Até agora, o processamento de texto pode levá-lo.

Então, eu também acho que os grupos de processos são o caminho a percorrer. Se você deseja que um processo seja executado em seu próprio grupo de processos, você pode usar a ferramenta 'pgrphack' do pacote Debian 'daemontools':

pgrphack my_command args

Ou você pode novamente recorrer ao Perl:

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

A única ressalva aqui é que os grupos de processos não se aninham; portanto, se algum processo estiver criando seus próprios grupos, seus subprocessos não estarão mais no grupo que você criou.

Jander
fonte
Os processos filhos são arbitrários e podem ou não usar os próprios grupos de processos (não posso assumir nada). No entanto, sua resposta chega mais perto do que parece possível de ser alcançado no Linux, então eu aceito. Obrigado.
STenyaK
Isso foi muito útil!
Michal Gallovic 11/02
Os pipes pstree também incluirão os IDs dos threads, ou seja, os IDs dos threads que um $ pid iniciou.
maxschlepzig
Você pode usar grep único: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
fonte
Seria só vale a pena notar que isso vai funcionar em escudos modernos ( bash, zsh, fish, e até mesmo ksh 99), mas pode não funcionar em conchas mais velhos, por exemploksh 88
grochmal
@ Grochmal, veja minha resposta abaixo para uma solução transversal que funciona no ksh-88.
precisa saber é o seguinte
1

A versão mais curta que encontrei também lida corretamente com comandos como pop3d:

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

Trata-se, erradamente, se você tem os comandos que têm nomes estranhos como: my(23)prog.

Ole Tange
fonte
1
Isso não funciona para comandos que estão executando algum encadeamento (porque o pstree imprime esses IDs também).
precisa saber é o seguinte
@maxschlepzig Percebeu muito problema com o ffmpeguso de threads. Embora, a partir de observações rápidas, pareça que os tópicos são dados com seu nome dentro de chaves { },.
Gypsy Spellweaver
1

Há também a questão da correção. Analisar ingenuamente a saída de pstreeé problemático por vários motivos:

  • pstree exibe PIDs e os IDs de threads (os nomes são mostrados entre chaves)
  • um nome de comando pode conter chaves, números entre parênteses que impossibilitam a análise confiável

Se você possui o Python e o psutilpacote instalado, pode usar este trecho para listar todos os processos descendentes:

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

(O pacote psutil é, por exemplo, instalado como uma dependência do tracercomando que está disponível no Fedora / CentOS.)

Como alternativa, você pode fazer uma travessia da árvore do processo pela primeira vez em uma casca de bourne:

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

Para calcular o fechamento transitivo de um pid, a parte da cauda pode ser omitida.

Observe que o acima não usa recursão e também é executado no ksh-88.

No Linux, pode-se eliminar a pgrepchamada e, em vez disso, ler as informações 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

Isso é mais eficiente, porque economizamos um fork / exec para cada PID e fazemos pgrepalgum trabalho adicional em cada chamada.

maxschlepzig
fonte
1

Esta versão do Linux precisa apenas de proc e ps. É adaptado da última parte da excelente resposta de @ maxschlepzig . Esta versão lê / proc diretamente do shell, em vez de gerar um subprocesso em um loop. É um pouco mais rápido e sem dúvida um pouco mais elegante, como o título deste tópico solicita.

#!/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
fonte
0

Em cada um dos seus dois casos de uso (aparentemente muito artificiais), por que você deseja eliminar alguns subprocessos de processos infelizes? Como você sabe melhor do que um processo quando seus filhos devem viver ou morrer? Isso parece um projeto ruim para mim; um processo deve limpar depois de si mesmo.

Se você realmente conhece melhor, deve estar criando esses subprocessos, e o 'processo daemonized' é aparentemente muito burro para se confiar fork(2).

Você deve evitar manter listas de processos filhos ou rastejar através da árvore de processos, por exemplo, colocando os processos filhos em um grupo de processos separado, conforme sugerido pelo @Gilles.

Em qualquer caso, suspeito que o seu processo daemon seria melhor criando um pool de threads de trabalho (que necessariamente morre junto com seu processo de contenção) do que uma árvore profunda de sub-sub-sub-processos, que algo em algum lugar precisa limpar .

AnotherSmellyGeek
fonte
2
Ambos os casos de uso são usados ​​em um ambiente de integração / teste contínuo, portanto, eles precisam lidar com a possibilidade de um bug existir no (s) processo (s) filho (s). Esse bug pode se manifestar como incapacidade de desligar a si próprio ou a seus filhos, por isso preciso de uma maneira de garantir que eu possa fechá-los todos no pior caso.
STenyaK
1
Nesse caso, estou com @Gilles e @Jander; grupos de processos são a melhor maneira.
AnotherSmellyGeek
0

Aqui está um script de wrapper pgrep que permite usar o pgrep e obter todos os descendentes ao mesmo tempo.

~/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 da mesma maneira que invocaria o pgrep normal, como pgrep_recursive -U $USER javapara encontrar todos os processos e subprocessos Java do usuário atual.

zeroimpl
fonte
1
Uma vez que este é o bash, tenho a sensação de que o código usado para unir as PIDs com o delimitador poderia ser substituído com ajuste IFSe usando matrizes ( "${array[*]}").
Muru