Вишукано отримати список нащадків процесів


23

Я хотів би отримати список усіх процесів, які відбуваються (наприклад, діти, онуки тощо) $pid. Це найпростіший спосіб, який я придумав:

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

Чи є якась команда чи простіший спосіб отримати повний список усіх нащадків?


Чи є причина, що вони вам потрібні в одному рядку? Що ви робите з цим результатом? У мене є відчуття, що це проблема xy, і ви задаєте неправильне запитання.
Йорданм

Мене не хвилює формат до тих пір, поки він чистий (тобто я не переймаюся '\n'розділеними та ' 'відмеженими). Практичний випадок використання: а) сценарій демонізатора, який я написав із чистого мазохізму (конкретно, функція "стоп" повинна мати справу з будь-яким деревом процесів, що породжував демонізований процес); і b) скрипт тайм-аута, який знищить будь-який час, який вдалося створити.
STenyaK

2
@STenyaK Ваші випадки використання змушують мене думати, що ви шукаєте групи процесів і негативний аргумент kill. Дивіться unix.stackexchange.com/questions/9480/… , unix.stackexchange.com/questions/50555/…
"SO- перестань бути злим"

@Gilles, за допомогою якого ps ax -opid,ppid,pgrp,cmdя бачу, існує багато процесів, які поділяють те саме, pgrpяк саме піддерево, яке я хочу вбити. (Крім того, я не можу побачити setpgrpпрограму в списку де - небудь в DEBiAN пакетів стабільних: packages.debian.org / ... )
STenyaK

1
Інший випадок використання: renice / ionice на цілому дереві технологічних процесів, який їсть занадто багато ресурсів, наприклад, велика паралельна збірка.
Гепард

Відповіді:


15

Далі дещо простіше і має додаткову перевагу ігнорування чисел у назвах команд:

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

Або з Perl:

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

Ми шукаємо номери в круглих дужках, щоб ми, наприклад, не давали 2 як дочірній процес, коли переходимо поперек gif2png(3012). Але якщо ім'я команди містить число, яке вводиться в дужках, всі ставки відміняються. Існує лише поки що обробка тексту може взяти вас.

Тому я також вважаю, що процесними групами є шлях. Якщо ви хочете запустити процес у власній групі процесів, ви можете скористатися інструментом 'pgrphack' з пакету Debian 'daemontools':

pgrphack my_command args

Або ви знову можете звернутися до Perl:

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

Єдине застереження тут полягає в тому, що групи процесів не гніздяться, тому, якщо якийсь процес створює власні групи процесів, його підпроцеси більше не будуть в тій групі, яку ви створили.


Дочірні процеси є довільними і можуть або не можуть використовувати самі групи процесів (я нічого не можу припустити). Однак ваша відповідь наближається до того, що бачимо досягти в Linux, тому я прийму це. Спасибі.
STenyaK

Це було дуже корисно!
Міхал Галлович

Труби pstree також включатимуть ідентифікатори потоку, тобто ідентифікатори ниток, які розпочав $ pid.
maxschlepzig

Ви можете використовувати одиночний pstree -lp | grep -Po "(?<=\()\d+(?=\))"
греп

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

Було б тільки відзначити , що це буде працювати на сучасних оболонках ( bash, zsh, fish, і навіть ksh 99), але може не працювати на старих снарядах, наприкладksh 88
grochmal

@grochmal, дивіться мою відповідь нижче щодо обхідного рішення, яке працює в ksh-88.
maxschlepzig

1

Я знайшов найкоротшу версію, яка також правильно розбирається з такими командами як pop3d:

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

Йдеться неправильно , якщо у вас є команди , які мають дивні імена , як: my(23)prog.


1
Це не працює для команд, які виконують якусь нитку (оскільки pstree також друкує ці ідентифікатори).
maxschlepzig

@maxschlepzig Помітив саме цю проблему з ffmpegвикористанням ниток. Хоча, з швидких спостережень, здається , що нитки дані з їх ім'ям всередині фігурних дужок { }.
Циганський заклинатель

1

Існує також питання правильності. Наївно проаналізувати вихідний сигнал pstreeпроблематично з кількох причин:

  • pstree відображає PID та ідентифікатори потоків (імена показані фігурними дужками)
  • ім'я команди може містити фігурні дужки, числа в дужках, які роблять неможливим надійний аналіз

Якщо у вас встановлений Python і psutilпакет, ви можете використовувати цей фрагмент для переліку всіх нащадків:

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

(Пакет psutil встановлюється, наприклад, як залежність від tracerкоманди, яка доступна у Fedora / CentOS.)

Крім того, ви можете здійснити обхід першого попереду дерев технологічного процесу в оболонці Bourne:

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

Для обчислення транзитивного замикання піда хвостова частина може бути опущена.

Зауважте, що вищезгадане не використовує рекурсії, а також працює в ksh-88.

В Linux можна усунути pgrepвиклик і замість цього прочитати інформацію з /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

Це ефективніше, оскільки ми заощаджуємо один вилок / exec для кожного PID та виконуємо pgrepдодаткову роботу в кожному виклику.


1

Ця версія Linux потрібна лише / proc і ps. Він адаптований з останнього фрагменту чудової відповіді @ maxschlepzig . Ця версія читає / обробляє безпосередньо з оболонки замість нерестування підпроцесу в циклі. Це трохи швидше і, можливо, трохи елегантніше, як цього вимагає назва теми.

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

У кожному з ваших двох (здавалося б, дуже штучних) випадків використання, чому ви хочете вбити деякі невдалі підпроцеси процесу? Як ви знаєте краще, ніж процес, коли його діти повинні жити чи вмирати? Мені це здається поганим дизайном; процес повинен очиститися після себе.

Якщо ви дійсно знаєте краще, то вам слід розганяти ці підпроцеси, і «демонізований процес», мабуть, занадто німий, щоб йому можна було довіряти fork(2).

Вам слід уникати ведення списків дочірніх процесів або пронизування дерев процесів, наприклад, розміщуючи дочірні процеси в окрему групу процесів, як це запропонував @Gilles.

У будь-якому випадку, я підозрюю, що вашому демонізованому процесу було б краще створити пул робочих ниток (який обов'язково вмирає разом із його процесом, що містить), ніж глибоке дерево під-підпроцесів, яке щось десь потім має очистити .


2
Обидва випадки використання використовуються в середовищі безперервної інтеграції / тестування, тому вони мають мати справу з можливістю появи помилок у дочірніх процесах. Ця помилка може проявлятися як неможливість належним чином закрити себе чи своїх дітей, тому мені потрібен спосіб переконатись, що я можу закрити їх усіх у гіршому випадку.
STenyaK

1
У такому випадку я з @Gilles та @Jander; Групи процесів - найкращий спосіб.
AnotherSmellyGeek

0

Ось сценарій обгортки pgrep, який дозволяє використовувати pgrep та отримувати всіх нащадків одночасно.

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

Викликайте так само, як і звичайний pgrep, наприклад, pgrep_recursive -U $USER javaщоб знайти всі процеси Java та підпроцеси у поточного користувача.


1
Оскільки це баш, у мене є відчуття, що код, який використовується для з'єднання PID з роздільником, може бути замінений на встановлення IFSта використання масивів ( "${array[*]}").
muru
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.