Liste der Nachkommenprozesse elegant abrufen

23

Ich möchte eine Liste aller Prozesse erhalten, von denen ich abstamme (z. B. Kinder, Enkelkinder usw.) $pid. Dies ist der einfachste Weg, den ich mir ausgedacht habe:

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

Gibt es einen Befehl oder eine einfachere Möglichkeit, die vollständige Liste aller untergeordneten Prozesse abzurufen?

STenyaK
quelle
Gibt es einen Grund, warum Sie alle in einer Zeile benötigen? Was machst du mit dieser Ausgabe? Ich habe das Gefühl, dass dies ein xy-Problem ist und Sie die falsche Frage stellen.
Jordanien
Das Format ist mir egal, solange es sauber ist (dh es ist mir egal '\n', ob es ' 'abgegrenzt oder abgegrenzt ist). Ein praktischer Anwendungsfall ist: a) ein Daemonizer-Skript, das ich aus reinem Masochismus geschrieben habe (insbesondere muss die "Stop" -Funktionalität sich mit jedem Prozessbaum befassen, den der daemonisierte Prozess hervorgebracht hat); und b) ein Zeitüberschreitungsskript, das alle durch die Zeitüberschreitung verursachten Vorgänge beendet.
STenyaK
2
@STenyaK Ihre Anwendungsfälle lassen mich denken, dass Sie nach Prozessgruppen und einem negativen Argument suchen kill. Siehe unix.stackexchange.com/questions/9480/... , unix.stackexchange.com/questions/50555/...
Gilles 'SO- Anschlag, die bösen'
@Gilles using ps ax -opid,ppid,pgrp,cmdIch sehe, dass es viele Prozesse gibt, die den gleichen pgrpTeilbaum haben, den ich töten möchte. (Außerdem kann ich das setpgrpProgramm nirgends in den stabilen
Debian-
1
Ein weiterer Anwendungsfall: Renice / Ionice für einen gesamten Prozessbaum, der zu viele Ressourcen verbraucht, z. B. einen großen parallelen Build.
Gepard

Antworten:

15

Das Folgende ist etwas einfacher und hat den zusätzlichen Vorteil, dass Zahlen in den Befehlsnamen ignoriert werden:

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

Oder mit Perl:

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

Wir suchen nach Zahlen in Klammern, damit wir beispielsweise keine 2 als untergeordneten Prozess angeben, wenn wir darauf stoßen gif2png(3012). Wenn der Befehlsname jedoch eine Zahl in Klammern enthält, sind alle Wetten deaktiviert. Es gibt nur so weit Textverarbeitung können Sie.

Daher denke ich auch, dass Prozessgruppen der richtige Weg sind. Wenn Sie möchten, dass ein Prozess in einer eigenen Prozessgruppe ausgeführt wird, können Sie das Tool 'pgrphack' aus dem Debian-Paket 'daemontools' verwenden:

pgrphack my_command args

Oder Sie könnten sich wieder an Perl wenden:

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

Die einzige Einschränkung besteht darin, dass Prozessgruppen nicht verschachtelt werden. Wenn also ein Prozess seine eigenen Prozessgruppen erstellt, befinden sich seine Unterprozesse nicht mehr in der von Ihnen erstellten Gruppe.

Jander
quelle
Untergeordnete Prozesse sind willkürlich und können Prozessgruppen selbst verwenden oder nicht (ich kann nichts annehmen). Wie auch immer, Ihre Antwort kommt dem am nächsten, was unter Linux erreichbar sein soll, also werde ich sie akzeptieren. Vielen Dank.
STenyaK
Das war sehr nützlich!
Michal Gallovic
Die pstree-Pipes enthalten auch die Thread-IDs, dh die IDs der Threads, mit denen ein $ pid gestartet wurde.
Maxschlepzig
Sie können einfach grep verwenden: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
quelle
Es wäre nur wert sein , unter Hinweis darauf , dass diese auf moderne Schalen arbeiten ( bash, zsh, fish, und sogar ksh 99), aber möglicherweise nicht auf älteren Muscheln, zBksh 88
grochmal
@grochmal, siehe meine Antwort unten für eine Traversal-Lösung, die in ksh-88 funktioniert.
Maxschlepzig
1

Die kürzeste Version, die ich gefunden habe, befasst sich auch korrekt mit Befehlen wie pop3d:

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

Es befasst sich falsch , wenn Sie Befehle haben , die seltsame Namen haben wie: my(23)prog.

Ole Tange
quelle
1
Dies funktioniert nicht für Befehle, die einen Thread ausführen (da pstree auch diese IDs druckt).
Maxschlepzig
@maxschlepzig Bemerkte das Problem mit der ffmpegVerwendung von Threads. Aus kurzen Beobachtungen geht hervor, dass die Fäden mit ihrem Namen in geschweiften Klammern angegeben sind { }.
Gypsy Spellweaver
1

Es gibt auch die Frage der Richtigkeit. Das einfache Parsen der Ausgabe von pstreeist aus mehreren Gründen problematisch:

  • pstree zeigt PIDs und die IDs von Threads an (Namen werden in geschweiften Klammern angezeigt)
  • Ein Befehlsname kann geschweifte Klammern und Zahlen in Klammern enthalten, die ein zuverlässiges Parsen unmöglich machen

Wenn Sie Python und das psutilPaket installiert haben, können Sie dieses Snippet verwenden, um alle nachfolgenden Prozesse aufzulisten:

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

(Das psutil-Paket wird zB als eine Abhängigkeit des tracerBefehls installiert, der auf Fedora / CentOS verfügbar ist.)

Alternativ können Sie den Prozessbaum in einer Bourneshell im ersten Schritt durchlaufen:

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

Zur Berechnung des Transitiv-Verschlusses einer PID kann der Endteil weggelassen werden.

Beachten Sie, dass der obige Befehl keine Rekursion verwendet und auch in ksh-88 ausgeführt wird.

Unter Linux kann man den pgrepAufruf unterdrücken und stattdessen die Informationen lesen von /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

Dies ist effizienter, da für jede PID ein Fork / Exec gespeichert wird und pgrepbei jedem Aufruf zusätzliche Arbeit geleistet wird.

maxschlepzig
quelle
1

Diese Linux-Version benötigt nur / proc und ps. Es ist aus dem letzten Stück von @ maxschlepzigs exzellenter Antwort angepasst . Diese Version liest / proc direkt aus der Shell, anstatt einen Unterprozess in einer Schleife zu erzeugen. Es ist ein bisschen schneller und wohl etwas eleganter, als der Titel dieses Threads verlangt.

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

Warum möchten Sie in jedem Ihrer beiden (scheinbar sehr künstlichen) Anwendungsfälle die Unterprozesse eines unglücklichen Prozesses beenden? Woher weißt du besser als ein Prozess, wann seine Kinder leben oder sterben sollten? Dies scheint mir ein schlechtes Design zu sein. Ein Prozess sollte nach sich selbst bereinigen.

Wenn Sie es wirklich besser wissen, sollten Sie diese Unterprozesse forken, und der "daemonisierte Prozess" ist anscheinend zu dumm, um ihm zu vertrauen fork(2).

Sie sollten vermeiden, Listen von untergeordneten Prozessen zu führen oder den Prozessbaum zu durchsuchen, z. B. indem Sie die untergeordneten Prozesse in eine separate Prozessgruppe einteilen, wie von @Gilles vorgeschlagen.

Ich vermute auf jeden Fall, dass Ihr daemonisierter Prozess besser dazu geeignet ist, einen Worker-Thread-Pool zu erstellen (der notwendigerweise zusammen mit dem enthaltenen Prozess abstirbt) als einen tiefen Baum von Sub-Sub-Sub-Prozessen, die irgendwo aufgeräumt werden müssen .

AnotherSmellyGeek
quelle
2
Beide Anwendungsfälle werden in einer kontinuierlichen Integrations- / Testumgebung verwendet, sodass sie die Möglichkeit eines Fehlers in den untergeordneten Prozessen berücksichtigen müssen. Dieser Fehler kann sich in der Unfähigkeit äußern, sich selbst oder ihre Kinder ordnungsgemäß herunterzufahren. Daher muss sichergestellt werden, dass ich sie im schlimmsten Fall alle schließen kann.
STenyaK
1
In diesem Fall bin ich bei @Gilles und @Jander; Prozessgruppen sind der beste Weg.
AnotherSmellyGeek
0

Hier ist ein pgrep-Wrapper-Skript, mit dem Sie pgrep verwenden und alle Nachkommen gleichzeitig abrufen können.

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

Rufen Sie auf die gleiche Weise auf, wie Sie normale pgrep aufrufen würden pgrep_recursive -U $USER java, um beispielsweise alle Java-Prozesse und -Unterprozesse des aktuellen Benutzers zu finden.

nullimpl
quelle
1
Da dies eine Bash ist, habe ich das Gefühl, dass der Code, der zum Verbinden der PIDs mit dem Begrenzer verwendet wird, durch Setzen IFSund Verwenden von Arrays ( "${array[*]}") ersetzt werden könnte.
muru