Elegancko uzyskaj listę procesów potomnych

23

Chciałbym uzyskać listę wszystkich procesów, które zstępują (np. Dzieci, wnuki itp.) $pid. Oto najprostszy sposób, jaki wymyśliłem:

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

Czy istnieje jakieś polecenie lub prostszy sposób na uzyskanie pełnej listy wszystkich procesów potomnych?

STenyaK
źródło
Czy istnieje powód, dla którego potrzebujesz ich wszystkich w jednym wierszu? Co robisz z tym wyjściem? Mam wrażenie, że to jest problem xy, a ty zadajesz złe pytanie.
Jordan
Nie dbam o format tak długo, jak jest czysty (tzn. Nie dbam o '\n'rozdzielanie czy ' 'rozdzielanie). Praktycznym przykładem użycia jest: a) skrypt demonizatora, który napisałem z czystego masochizmu (konkretnie, funkcja „stop” ma do czynienia z dowolnym drzewem procesów, które pojawił się proces demonizowany); oraz b) skrypt przekroczenia limitu czasu, który zabije wszystko, co udało się stworzyć proces przekroczenia limitu czasu.
STenyaK,
2
@STenyaK Twoje przypadki użycia sprawiają, że myślę, że szukasz grup procesów i argumentu negatywnego kill. Zobacz unix.stackexchange.com/questions/9480/… , unix.stackexchange.com/questions/50555/...
Gilles 'SO - przestań być zły'
@Gilles przy użyciu ps ax -opid,ppid,pgrp,cmdWidzę, że istnieje wiele procesów, które współużytkują dokładnie to samo pgrppoddrzewo, które chcę zabić. (Dodatkowo nie widzę setpgrpprogramu nigdzie wymienionego w pakietach stabilnych Debiana: packages.debian.org/… )
STenyaK
1
Kolejny przypadek użycia: renice / ionice na całym drzewie procesów, które zjadają zbyt wiele zasobów, np. Dużą równoległą kompilację.
Gepard

Odpowiedzi:

15

Poniższe jest nieco prostsze i ma tę dodatkową zaletę, że ignoruje liczby w nazwach poleceń:

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

Lub z Perlem:

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

Szukamy liczb w nawiasach, aby na przykład nie podawać 2 jako procesu potomnego, gdy natkniemy się na gif2png(3012). Ale jeśli nazwa polecenia zawiera liczbę w nawiasach, wszystkie zakłady są wyłączone. Tylko do tej pory przetwarzanie tekstu może Cię zabrać.

Myślę też, że grupy procesowe są do zrobienia. Jeśli chcesz uruchomić proces we własnej grupie procesów, możesz użyć narzędzia „pgrphack” z pakietu Debian „daemontools”:

pgrphack my_command args

Lub możesz ponownie zwrócić się do Perla:

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

Jedynym zastrzeżeniem jest to, że grupy procesów nie zagnieżdżają się, więc jeśli jakiś proces tworzy własne grupy procesów, jego podprocesy nie będą już w grupie, którą utworzyłeś.

Jander
źródło
Procesy potomne są arbitralne i mogą same używać grup procesów (nie mogę niczego założyć). Jednak twoja odpowiedź jest najbliższa temu, co wydaje się możliwe do osiągnięcia w Linuksie, więc zaakceptuję to. Dzięki.
STenyaK
To było bardzo przydatne!
Michal Gallovic
Rury pstree będą również zawierać identyfikatory wątków, tj. Identyfikatory wątków, które rozpoczął $ pid.
maxschlepzig
Możesz użyć pojedynczego 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
}
Russell Davis
źródło
Byłoby tylko warto zauważyć, że to będzie działać na nowoczesnych pocisków ( bash, zsh, fish, a nawet ksh 99), ale może nie działać na starszych muszli, na przykładksh 88
Grochmal
@grochmal, zobacz moją odpowiedź poniżej dla rozwiązania przejścia, które działa w ksh-88.
maxschlepzig
1

Najkrótsza wersja, którą znalazłem, która również poprawnie obsługuje polecenia takie jak pop3d:

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

Zajmuje błędnie jeśli masz polecenia, które mają dziwne nazwy, jak: my(23)prog.

Ole Tange
źródło
1
Nie działa to dla poleceń, które uruchamiają jakiś wątek (ponieważ pstree drukuje również te identyfikatory).
maxschlepzig
@maxschlepzig Zauważyłem, że to bardzo problem z ffmpegużywaniem wątków. Choć od szybkich uwag, wydaje się, że wątki są podane wraz z nazwą wewnątrz nawiasów klamrowych, { }.
Gypsy
1

Istnieje również kwestia poprawności. Naiwne analizowanie wyników pstreejest problematyczne z kilku powodów:

  • pstree wyświetla PID i identyfikatory wątków (nazwy wyświetlane są w nawiasach klamrowych)
  • nazwa polecenia może zawierać nawiasy klamrowe, liczby w nawiasach uniemożliwiające niezawodne parsowanie

Jeśli masz psutilzainstalowany Python i pakiet, możesz użyć tego fragmentu kodu, aby wyświetlić listę wszystkich procesów potomnych:

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

(Pakiet psutil jest np. Instalowany jako zależność tracerpolecenia dostępnego na Fedorze / CentOS.)

Alternatywnie możesz wykonać pierwsze przejście drzewa przetwarzania w powłoce Bourne'a:

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

W celu obliczenia przechodniego zamknięcia pid część ogonową można pominąć.

Zauważ, że powyższe nie używa rekurencji i działa również w ksh-88.

W systemie Linux można wyeliminować pgreppołączenie i zamiast tego odczytać informacje z /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

Jest to bardziej wydajne, ponieważ zapisujemy jeden fork / exec dla każdego PID i pgrepwykonujemy dodatkową pracę w każdym wywołaniu.

maxschlepzig
źródło
1

Ta wersja systemu Linux wymaga tylko / proc i ps. Jest to adaptacja ostatniego fragmentu doskonałej odpowiedzi @ maxschlepzig . Ta wersja czyta / proc bezpośrednio z powłoki zamiast odradzania podprocesu w pętli. Jest to nieco szybsze i prawdopodobnie nieco bardziej eleganckie, jak wymaga tego tytuł wątku.

#!/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
źródło
0

W każdym z dwóch (pozornie bardzo sztucznych) przypadków użycia, dlaczego chcesz zabić podprocesy jakiegoś niefortunnego procesu? Skąd wiesz lepiej niż proces, kiedy jego dzieci powinny żyć lub umrzeć? Wydaje mi się to złym projektem; proces powinien po sobie posprzątać.

Jeśli naprawdę wiesz lepiej, powinieneś rozwidlać te podprocesy, a „proces demonizowany” jest najwyraźniej zbyt głupi, aby można było mu zaufać fork(2).

Powinieneś unikać prowadzenia list procesów potomnych lub przeszukiwania drzewa procesów, np. Umieszczając procesy potomne w osobnej grupie procesów, jak sugeruje @Gilles.

W każdym razie podejrzewam, że lepiej byłoby, gdyby twój demonizowany proces stworzył pulę wątków roboczych (która z konieczności umiera wraz z zawartym w niej procesem) niż głębokie drzewo pod-podprocesów, które coś gdzieś musi następnie wyczyścić .

AnotherSmellyGeek
źródło
2
Oba przypadki użycia są używane w środowisku ciągłej integracji / testowania, więc muszą poradzić sobie z możliwością wystąpienia błędu w procesach potomnych. Ten błąd może objawiać się jako niezdolność do prawidłowego zamknięcia siebie lub swoich dzieci, dlatego potrzebuję sposobu, aby upewnić się, że mogę je wszystkie zamknąć w najgorszym przypadku.
STenyaK
1
W takim razie jestem z @Gilles i @Jander; grupy procesów są najlepszym sposobem.
AnotherSmellyGeek,
0

Oto skrypt otoki pgrep, który pozwala używać pgrep i uzyskiwać wszystkich potomków jednocześnie.

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

Wywołaj w taki sam sposób, jak przy normalnym pgrep, na przykład w pgrep_recursive -U $USER javacelu znalezienia wszystkich procesów Java i podprocesów od bieżącego użytkownika.

zeroimpl
źródło
1
Ponieważ jest to bash, mam wrażenie, że kod użyty do połączenia PID z separatorem może zostać zastąpiony ustawieniem IFSi użyciem tablic ( "${array[*]}").
muru