Obtenez élégamment la liste des processus descendants


23

Je voudrais obtenir une liste de tous les processus qui en découlent (par exemple les enfants, les petits-enfants, etc.) $pid. C'est la manière la plus simple que j'ai trouvée:

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

Existe-t-il une commande ou un moyen plus simple d'obtenir la liste complète de tous les processus descendants?


Y a-t-il une raison pour laquelle vous en avez besoin sur une seule ligne? Que faites-vous avec cette sortie? J'ai le sentiment qu'il s'agit d'un problème xy et que vous posez la mauvaise question.
jordanm

Je ne me soucie pas du format tant qu'il est propre (c'est-à-dire que je ne me soucie pas de '\n'délimité vs ' 'délimité). Le cas d'utilisation pratique est: a) un script démoniseur que j'ai écrit à partir du masochisme pur (en particulier, la fonctionnalité "stop" doit gérer n'importe quelle arborescence de processus engendrée par le processus démonisé); et b) un script de temporisation qui tuera tout ce que le processus de temporisation a réussi à créer.
STenyaK

2
@STenyaK Vos cas d'utilisation me font penser que vous recherchez des groupes de processus et un argument négatif kill. Voir unix.stackexchange.com/questions/9480/… , unix.stackexchange.com/questions/50555/…
Gilles 'SO- arrête d'être méchant'

@Gilles utilisant ps ax -opid,ppid,pgrp,cmdJe vois qu'il existe de nombreux processus qui partagent le même pgrpque le sous-arbre exact que je veux tuer. (De plus, je ne vois aucun setpgrpprogramme répertorié dans les paquets stables de Debian: packages.debian.org/… )
STenyaK

1
Un autre cas d'utilisation: renice / ionice sur un arbre de processus entier qui consomme trop de ressources, par exemple une grande construction parallèle.
Cheetah

Réponses:


15

Ce qui suit est un peu plus simple et présente l'avantage supplémentaire d'ignorer les nombres dans les noms de commande:

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

Ou avec Perl:

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

Nous recherchons des nombres entre parenthèses afin de ne pas, par exemple, donner 2 comme processus enfant lorsque nous traversons gif2png(3012). Mais si le nom de la commande contient un nombre entre parenthèses, tous les paris sont désactivés. Il n'y a que jusqu'ici que le traitement de texte puisse vous emmener.

Je pense donc aussi que les groupes de processus sont la voie à suivre. Si vous souhaitez qu'un processus s'exécute dans son propre groupe de processus, vous pouvez utiliser l'outil 'pgrphack' du paquet Debian 'daemontools':

pgrphack my_command args

Ou vous pouvez à nouveau vous tourner vers Perl:

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

La seule mise en garde ici est que les groupes de processus ne sont pas imbriqués, donc si un processus crée ses propres groupes de processus, ses sous-processus ne seront plus dans le groupe que vous avez créé.


Les processus enfants sont arbitraires et peuvent ou non utiliser des groupes de processus eux-mêmes (je ne peux rien supposer). Cependant, votre réponse se rapproche le plus de ce qui semble réalisable sous Linux, donc je l'accepterai. Merci.
STenyaK

C'était très utile!
Michal Gallovic

Les canaux pstree incluront également les identifiants des threads, c'est-à-dire les ID des threads lancés par $ pid.
maxschlepzig

Vous pouvez utiliser un seul 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
}

Il ne serait intéressant de noter que cela fonctionnera sur des coquilles modernes ( bash, zsh, fish, et même ksh 99), mais pourrait ne pas fonctionner sur des coquilles plus âgés, par exempleksh 88
grochmal

@grochmal, voir ma réponse ci-dessous pour une solution de traversée qui fonctionne dans ksh-88.
maxschlepzig

1

La version la plus courte que j'ai trouvée qui traite également correctement des commandes telles que pop3d:

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

Il traite à tort si vous avez des commandes qui ont des noms étranges comme: my(23)prog.


1
Cela ne fonctionne pas pour les commandes qui exécutent un thread (car pstree imprime également ces ID).
maxschlepzig

@maxschlepzig A remarqué ce problème avec l' ffmpegutilisation de threads. Bien que, à partir d' observations rapides, il semble que les fils sont données avec leur nom à l' intérieur des accolades, { }.
Gypsy Spellweaver

1

Il y a aussi la question de l'exactitude. L'analyse naïve de la sortie de pstreeest problématique pour plusieurs raisons:

  • pstree affiche les PID et les identifiants des threads (les noms sont indiqués entre accolades)
  • un nom de commande peut contenir des accolades, des nombres entre parenthèses qui rendent impossible une analyse fiable

Si Python et le psutilpackage sont installés, vous pouvez utiliser cet extrait pour répertorier tous les processus descendants:

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

(Le package psutil est par exemple installé en tant que dépendance de la tracercommande disponible sur Fedora / CentOS.)

Alternativement, vous pouvez faire une traversée en largeur de l'arbre de processus dans un shell bourne:

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

Pour calculer la fermeture transitive d'un pid, la partie de queue peut être omise.

Notez que ce qui précède n'utilise pas la récursivité et s'exécute également dans ksh-88.

Sous Linux, on peut éliminer l' pgrepappel et lire à la place les informations 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

Ceci est plus efficace car nous enregistrons un fork / exec pour chaque PID et effectuons un pgreptravail supplémentaire à chaque appel.


1

Cette version Linux nécessite uniquement / proc et ps. Il est adapté du dernier morceau de l'excellente réponse de @ maxschlepzig . Cette version lit / proc directement à partir du shell au lieu de générer un sous-processus dans une boucle. C'est un peu plus rapide et sans doute légèrement plus élégant, comme le demande ce titre de fil.

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

Dans chacun de vos deux cas d'utilisation (apparemment très artificiels), pourquoi voulez-vous tuer des sous-processus de processus malheureux? Comment savez-vous mieux qu'un processus quand ses enfants devraient vivre ou mourir? Cela me semble une mauvaise conception; un processus doit se nettoyer après lui-même.

Si vous savez vraiment mieux, alors vous devriez bifurquer ces sous-processus, et le «processus démonisé» est apparemment trop stupide pour être fiable fork(2).

Vous devez éviter de conserver des listes de processus enfants ou de vous déplacer dans l'arborescence des processus, par exemple en plaçant les processus enfants dans un groupe de processus séparé comme suggéré par @Gilles.

Dans tous les cas, je soupçonne que votre processus démonisé serait préférable de créer un pool de threads de travail (qui meurt nécessairement avec son processus contenant) qu'un arbre profond de sous-sous-sous-sous-processus, que quelque chose doit ensuite nettoyer quelque part .


2
Les deux cas d'utilisation sont utilisés dans un environnement d'intégration / test continu, ils doivent donc faire face à la possibilité d'un bogue existant dans le ou les processus enfants. Ce bogue peut se manifester comme une incapacité à s'éteindre correctement eux-mêmes ou leurs enfants, j'ai donc besoin d'un moyen de m'assurer de pouvoir les fermer tous dans le pire des cas.
STenyaK

1
Dans ce cas, je suis avec @Gilles et @Jander; les groupes de processus sont le meilleur moyen.
AnotherSmellyGeek

0

Voici un script wrapper pgrep qui vous permet d'utiliser pgrep et d'obtenir tous les descendants en même temps.

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

Appelez de la même manière que vous appelez pgrep normal, de manière pgrep_recursive -U $USER javaà trouver tous les processus et sous-processus Java de l'utilisateur actuel.


1
Puisque c'est bash, j'ai le sentiment que le code utilisé pour joindre les PID avec le délimiteur pourrait être remplacé par la définition IFSet l'utilisation de tableaux ( "${array[*]}").
muru
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.