Элегантно получить список процессов-потомков

23

Я хотел бы получить список всех процессов, которые происходят (например, дети, внуки и т. Д.) $pid. Это самый простой способ, которым я придумал:

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

Есть ли какая-либо команда или более простой способ получить полный список всех процессов-потомков?

STenyaK
источник
Есть ли причина, по которой они нужны вам на одной линии? Что вы делаете с этим выходом? У меня такое ощущение, что это проблема xy, и вы задаете не тот вопрос.
Иордания
Мне плевать на формат, пока он чистый (то есть меня не волнует '\n'разделитель и ' 'разделитель). Практический пример использования: а) сценарий демона, который я написал из чистого мазохизма (в частности, функциональность «стоп» должна иметь дело с любым деревом процессов, порожденных демонизированным процессом); и b) сценарий тайм-аута, который уничтожит все, что удалось создать процессу тайм-аута.
STenyaK
2
@STenyaK Ваши варианты использования заставляют меня думать, что вы ищете группы процессов и отрицательный аргумент kill. См. Unix.stackexchange.com/questions/9480/… , unix.stackexchange.com/questions/50555/…
Жиль "
Использование @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

Единственное предостережение здесь заключается в том, что группы процессов не вкладываются, поэтому, если какой-то процесс создает свои собственные группы процессов, его подпроцессы больше не будут находиться в созданной вами группе.

Jander
источник
Дочерние процессы являются произвольными и могут использовать или не использовать сами группы процессов (я не могу ничего предположить). Однако ваш ответ наиболее близок к тому, что кажется достижимым в Linux, поэтому я его приму. Спасибо.
СтеняК
Это было очень полезно!
Михал Галлович
Каналы pstree также будут включать идентификаторы потоков, то есть идентификаторы потоков, запущенных $ pid.
maxschlepzig
Вы можете использовать один 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
}
Рассел Дэвис
источник
Было бы только отметить , что это будет работать на современных оболочках ( 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выполняем некоторую дополнительную работу в каждом вызове.

maxschlepzig
источник
1

Эта версия Linux требует только / proc и ps. Это адаптировано из последней части превосходного ответа @ maxschlepzig . Эта версия читает / proc непосредственно из оболочки вместо порождения подпроцесса в цикле. Это немного быстрее и, возможно, немного более элегантно, как этого требует заголовок темы.

#!/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
источник
0

Почему в каждом из ваших двух (казалось бы, очень искусственных) вариантов использования вы хотите убить подпроцессы какого-нибудь неудачного процесса? Как вы знаете лучше, чем процесс, когда его дети должны жить или умереть? Это кажется плохим дизайном для меня; процесс должен убирать за собой.

Если вы действительно знаете лучше, тогда вы должны отказаться от этих подпроцессов, и «демонизированный процесс», очевидно, слишком глуп, чтобы ему доверять fork(2).

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

В любом случае, я подозреваю, что вашему демонизированному процессу было бы лучше создать пул рабочих потоков (который обязательно умирает вместе с процессом, в котором он содержится), чем глубокое дерево подпод-подпроцессов, которое затем нужно что-то очистить ,

AnotherSmellyGeek
источник
2
Оба варианта использования используются в среде непрерывной интеграции / тестирования, поэтому им приходится иметь дело с возможностью наличия ошибки в дочернем процессе (ах). Эта ошибка может проявляться как неспособность должным образом себя или своих детей отключения, так что я нужен способ , чтобы обеспечить , что я могу закрыть их все в самом худшем случае.
СтеняК
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 от текущего пользователя.

zeroimpl
источник
1
Так как это bash, я чувствую, что код, используемый для объединения PID с разделителем, можно заменить настройкой IFSи использованием массивов ( "${array[*]}").
muru