Ottieni in modo elegante l'elenco dei processi discendenti


23

Vorrei ottenere un elenco di tutti i processi da cui discendono (ad es. Bambini, nipoti, ecc.) $pid. Questo è il modo più semplice con cui mi sono inventato:

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

Esiste un comando o un modo più semplice per ottenere l'elenco completo di tutti i processi discendenti?


C'è un motivo per cui ti servono tutti su una linea? Che cosa stai facendo con quell'output? Ho la sensazione che questo sia un problema xy, e tu stai facendo la domanda sbagliata.
Giordania,

Non mi interessa il formato purché sia ​​pulito (ovvero non mi interessa '\n'delimitato o ' 'delimitato). Il caso d'uso pratico è: a) uno script daemonizer che ho scritto per puro masochismo (in particolare, la funzionalità "stop" ha a che fare con qualsiasi albero di processi generato dal processo demonizzato); e b) uno script di timeout che ucciderà qualunque cosa il processo di timeout sia riuscito a creare.
StenyaK,

2
@STenyaK I tuoi casi d'uso mi fanno pensare che stai cercando gruppi di processi e un argomento negativo per kill. Vedi unix.stackexchange.com/questions/9480/… , unix.stackexchange.com/questions/50555/…
Gilles 'SO- smetti di essere malvagio'

@Gilles usando ps ax -opid,ppid,pgrp,cmdvedo che ci sono molti processi che condividono lo stesso pgrpsottoalbero esatto che voglio uccidere. (Inoltre, non riesco a vedere il setpgrpprogramma elencato da nessuna parte nei pacchetti debian stable: packages.debian.org/… )
STenyaK

1
Un altro caso d'uso: renice / ionice su un intero albero di processo che consuma troppe risorse, ad esempio una grande build parallela.
Ghepardo,

Risposte:


15

Quanto segue è in qualche modo più semplice e presenta l'ulteriore vantaggio di ignorare i numeri nei nomi dei comandi:

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'

Stiamo cercando numeri tra parentesi in modo da non dare, ad esempio, 2 come processo figlio quando ci imbattiamo gif2png(3012). Ma se il nome del comando contiene un numero tra parentesi, tutte le scommesse sono disattivate. C'è solo finora l'elaborazione del testo che può portarti.

Quindi penso anche che i gruppi di processi siano la strada da percorrere. Se desideri che un processo venga eseguito nel suo gruppo di processi, puoi usare lo strumento 'pgrphack' dal pacchetto Debian 'daemontools':

pgrphack my_command args

Oppure potresti rivolgerti di nuovo a Perl:

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

L'unica avvertenza qui è che i gruppi di processi non si annidano, quindi se alcuni processi stanno creando i propri gruppi di processi, i suoi sottoprocessi non saranno più nel gruppo creato.


I processi secondari sono arbitrari e possono o meno utilizzare i gruppi di processi stessi (non posso assumere nulla). Comunque la tua risposta è la più vicina a ciò che si vede essere realizzabile in Linux, quindi accetterò. Grazie.
StenyaK,

Questo è stato molto utile!
Michal Gallovic,

Le pipe pstree includeranno anche gli ID thread, ovvero gli ID dei thread avviati da $ pid.
maxschlepzig,

Puoi usare single grep:pstree -lp | grep -Po "(?<=\()\d+(?=\))"
puchu

7
descendent_pids() {
    pids=$(pgrep -P $1)
    echo $pids
    for pid in $pids; do
        descendent_pids $pid
    done
}

Sarebbe solo la pena notare che questo funziona su gusci moderni ( bash, zsh, fish, e anche ksh 99), ma potrebbe non funzionare su conchiglie più grandi, ad esempioksh 88
grochmal

@grochmal, vedi la mia risposta di seguito per una soluzione trasversale che funziona in ksh-88.
Maxschlepzig,

1

La versione più corta che ho trovato che si occupa anche correttamente di comandi come pop3d:

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

Si occupa torto se si dispone di comandi che hanno nomi strani come: my(23)prog.


1
Questo non funziona per i comandi che eseguono alcuni thread (perché anche pstree stampa quegli ID).
maxschlepzig,

@maxschlepzig Ho notato questo problema con l' ffmpegutilizzo dei thread. Però, da rapide osservazioni, sembra che i fili sono indicati con il loro nome all'interno delle parentesi graffe, { }.
Gypsy

1

C'è anche il problema della correttezza. L'analisi ingenua dell'output di pstreeè problematica per diversi motivi:

  • pstree visualizza i PID e gli ID dei thread (i nomi sono mostrati tra parentesi graffe)
  • un nome di comando potrebbe contenere parentesi graffe, numeri tra parentesi che rendono impossibile l'analisi affidabile

Se hai Python e il psutilpacchetto installato, puoi utilizzare questo frammento per elencare tutti i processi discendenti:

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

(Il pacchetto psutil è ad esempio installato come dipendenza del tracercomando che è disponibile su Fedora / CentOS.)

In alternativa, è possibile eseguire una prima traversata dell'albero di processo in una shell bourne:

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

Per calcolare la chiusura transitiva di un pid, la parte di coda può essere omessa.

Nota che quanto sopra non usa la ricorsione e funziona anche in ksh-88.

Su Linux, è possibile eliminare la pgrepchiamata e invece leggere le informazioni da /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

Questo è più efficiente perché salviamo un fork / exec per ogni PID e facciamo pgrepqualche lavoro aggiuntivo in ogni chiamata.


1

Questa versione di Linux richiede solo / proc e ps. È adattato dall'ultimo pezzo dell'eccellente risposta di @ maxschlepzig . Questa versione legge / proc direttamente dalla shell invece di generare un sotto-processo in un ciclo. È un po 'più veloce e probabilmente leggermente più elegante, come richiede questo titolo di discussione.

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

0

In ciascuno dei tuoi due casi d'uso (apparentemente molto artificiali), perché vuoi uccidere alcuni sottoprocessi di processi sfortunati? Come fai a sapere meglio di un processo in cui i suoi figli dovrebbero vivere o morire? Mi sembra un design scadente; un processo dovrebbe ripulire dopo se stesso.

Se lo sai davvero meglio, allora dovresti biforcare questi sottoprocessi e il "processo demone" è apparentemente troppo stupido per essere attendibile fork(2).

Dovresti evitare di tenere un elenco di processi figlio o di frugare nella struttura dei processi, ad esempio inserendo i processi figlio in un gruppo di processi separato, come suggerito da @Gilles.

In ogni caso, sospetto che il tuo processo daemonizzato sarebbe meglio creare un pool di thread di lavoro (che necessariamente muore insieme al suo processo di contenimento) rispetto a un albero profondo di sub-sub-sub-processi, che qualcosa da qualche parte deve poi ripulire .


2
Entrambi i casi d'uso sono utilizzati in un ambiente di integrazione / test continuo, quindi devono affrontare la possibilità di un bug esistente nel / i processo / i figlio. Questo bug può manifestarsi come incapacità di chiudere correttamente se stessi o i loro figli, quindi ho bisogno di un modo per assicurarmi di poterli chiudere tutti nel caso peggiore.
STenyaK,

1
In tal caso, sono con @Gilles e @Jander; i gruppi di processi sono il modo migliore.
AnotherSmellyGeek,

0

Ecco uno script wrapper pgrep che ti consente di usare pgrep e ottenere tutti i discendenti allo stesso 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

Richiamare nello stesso modo in cui si invocherebbe il normale pgrep, ad esempio pgrep_recursive -U $USER javaper trovare tutti i processi e i processi secondari Java dell'utente corrente.


1
Dato che questo è bash, ho la sensazione che il codice usato per unire i PID con il delimitatore possa essere sostituito con l'impostazione IFSe l'uso di array ( "${array[*]}").
muru,
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.