PS: Как я могу рекурсивно получить весь дочерний процесс для данного PID

Как я могу получить все дерево процессов, порожденное данным процессом, отображаемым как дерево, и только это дерево, то есть никакие другие процессы?

Вывод может выглядеть, например, как

 4378 ?        Ss     0:10 SCREEN
 4897 pts/16   Ss     0:00  \_ -/bin/bash
25667 pts/16   S+     0:00  |   \_ git diff
25669 pts/16   S+     0:00  |       \_ less -FRSX
11118 pts/32   Ss+    0:00  \_ -/bin/bash
11123 pts/32   S+     0:00      \_ vi

Я не мог получить желаемый результат чисто с параметрами ps,

Следующее дает желаемый результат, но кажется немного сложным:

#!/bin/bash

pidtree() {
  echo -n $1 " "
  for _child in $(ps -o pid --no-headers --ppid $1); do
    echo -n $_child `pidtree $_child` " "
  done
}

ps f `pidtree 4378`

У кого-нибудь есть более простое решение?

15 ответов

pstree Это очень хорошее решение, но оно немного сдержано. я использую ps --forest вместо. Но не для PID (-p) потому что он печатает только определенный процесс, но для сеанса (-g). Может распечатать любую информацию ps может печатать в художественном дереве ASCII, определяющем -o вариант.

Итак, мое предложение по этой проблеме:

ps --forest -o pid,tty,stat,time,cmd -g 2795

Если процесс не является лидером сеанса, то нужно применить немного больше трюка:

ps --forest -o pid,tty,stat,time,cmd -g $(ps -o sid= -p 2795)

Сначала он получает идентификатор сеанса (SID) текущего процесса, а затем снова вызывает ps с этим sid.

Если заголовки столбцов не нужны, добавьте "=" после каждого определения столбца в параметрах "-o", например:

ps --forest -o pid=,tty=,stat=,time=,cmd= -g $(ps -o sid= -p 2795)

Пример запуска и результат:

$ ps --forest -o pid=,tty=,stat=,time=,cmd= -g $(ps -o sid= -p 30085)
27950 pts/36   Ss   00:00:00 -bash
30085 pts/36   S+   00:00:00  \_ /bin/bash ./loop.sh
31888 pts/36   S+   00:00:00      \_ sleep 5

К сожалению, это не работает для screen так как он устанавливает sid для каждого дочернего экрана и всех внуков.

Чтобы все процессы порождались процессом, нужно построить все дерево. Я использовал awk для этого. Сначала он создает массив хешей, содержащий все PID => ,child,child..., В конце она вызывает рекурсивную функцию для извлечения всех дочерних процессов данного процесса. Результат передается другому ps отформатировать результат. Фактический PID должен быть записан как аргумент в awk вместо <PID>:

ps --forest $(ps -e --no-header -o pid,ppid|awk -vp=<PID> 'function r(s){print s;s=a[s];while(s){sub(",","",s);t=s;sub(",.*","",t);sub("[0-9]+","",s);r(t)}}{a[$2]=a[$2]","$1}END{r(p)}')

Для процесса SCREEN (pid=8041) пример вывода выглядит следующим образом:

  PID TTY      STAT   TIME COMMAND
 8041 ?        Ss     0:00 SCREEN
 8042 pts/8    Ss     0:00  \_ /bin/bash
 8092 pts/8    T      0:00      \_ vim test_arg test_server
12473 pts/8    T      0:00      \_ vim
12972 pts/8    T      0:00      \_ vim
pstree ${pid}

где ${pid} - это pid родительского процесса.

На gentoo pstree находится в пакете "psmisc", по-видимому, расположенном по адресу http://psmisc.sourceforge.net/

Вот моя версия, которая запускается мгновенно (потому что ps выполняется только один раз). Работает в bash и zsh.

pidtree() (
    [ -n "$ZSH_VERSION"  ] && setopt shwordsplit
    declare -A CHILDS
    while read P PP;do
        CHILDS[$PP]+=" $P"
    done < <(ps -e -o pid= -o ppid=)

    walk() {
        echo $1
        for i in ${CHILDS[$1]};do
            walk $i
        done
    }

    for i in "$@";do
        walk $i
    done
)

ps -H -g "$pid" -o comm

дерево не добавляется само по себе, это просто список процессов.

дает например

COMMAND
bash
  nvim
    python

Я создал небольшой скрипт bash для создания списков pid дочернего процесса (ов) родителя. Рекурсивно, пока не найдет последний дочерний процесс, у которого нет дочерних процессов. Это не дает вам вид дерева. Он просто перечисляет все pid.

function list_offspring {
  tp=`pgrep -P $1`          #get childs pids of parent pid
  for i in $tp; do          #loop through childs
    if [ -z $i ]; then      #check if empty list
      exit                  #if empty: exit
    else                    #else
      echo -n "$i "         #print childs pid
      list_offspring $i     #call list_offspring again with child pid as the parent
    fi;
  done
}
list_offspring $1

Первый аргумент list_offspring - родительский pid

Я работал, чтобы найти решение точно такой же проблемы. По сути, ps manpage не документирует никаких опций, позволяющих делать то, что мы хотим, с помощью одной команды. Вывод: нужен скрипт.

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

pidtree() {
  local parent=$1
  local list=
  while [ "$parent" ] ; do     
    if [ -n "$list" ] ; then
      list="$list,$parent"
    else
      list="$parent"
    fi
    parent=$(ps --ppid $parent -o pid h)
  done
  ps -f -p $list f
}

Вы можете получить pids всех дочерних процессов данного родительского процесса <pid> прочитав /proc/<pid>/task/<tid>/children вход.

Этот файл содержит идентификаторы дочерних процессов первого уровня. Рекурсивно вы можете видеть и для детей.

Для получения дополнительной информации перейдите на https://lwn.net/Articles/475688/.

Это дает только pid+children pids на каждой строке, что полезно для дальнейшей обработки.

pidtree() {
    for _pid in "$@"; do
        echo $_pid
        pidtree `ps --ppid $_pid -o pid h`
    done
}

По пути в командной строке:

ps -o time,pid,ppid,cmd --forest -g -p $(pgrep -x bash)

это выводит:

    TIME   PID  PPID CMD
00:00:00  5484  5480 bash
00:00:01  5531  5484  \_ emacs -nw .bashrc
00:00:01  2986  2984 /bin/bash
00:00:00  4731  2986  \_ redshift
00:00:00  5543  2986  \_ ps -o time,pid,ppid,cmd --forest -g -p 2986 5484

Более элегантный способ этого способа заключается в определении функции в .bashrc:

function subps()                                                                                    
{                                                                                                   
    process=$(pgrep -x $1)                                                                                                                                                                     
    ps -o time,pid,ppid,cmd --forest -g -p $process                                                 
}    

затем в командной строке запустите:

subps bash

Решение @xzfc ( /questions/31498/ps-kak-ya-mogu-rekursivno-poluchit-ves-dochernij-protsess-dlya-dannogo-pid/31523#31523) работает. Однако он использует некоторые особенности оболочки (например, shopt, ассоциативный массив). Он также имеет специальную обработку для zsh.

Я адаптировал его, чтобы сделать его совместимым с POSIX:

      pidtree() (
    # lines of "parent child"
    _PID_GRAPH="$(ps -e -o ppid= -o pid=)"

    _walk() {
        echo $1
        # iterate direct children of "$1"
        # awk: if the parent field matches $1, then print child field
        echo "$_PID_GRAPH" | awk -F' ' "\$1==\"$1\" {print \$2;}" | while read child_pid; do
            _walk "$child_pid"
        done
    }

    for i in "$@"; do _walk $i; done
)

Если мы хотим увидеть всех дочерних и всех дочерних элементов процесса, первый ответ не работает. Первый ответ предполагает, что все дочерние и дочерние элементы имеют одинаковый идентификатор сеанса. Это неверно.

Пример: возьмем pid=17601.

        $ ps --forest -o pid,ppid,pgrp,sess,tty,stat,time,cmd -g $(ps -o sid= -p 17601)
  PID  PPID  PGRP  SESS TT       STAT     TIME CMD
17601  1109 17601 17601 ?        Ss   00:00:00 sshd: vasya [priv]
17652 17601 17601 17601 ?        S    00:00:00  \_ sshd: vasya@pts/17

поэтому мы берем процесс с pid=17601, находим его идентификатор сеанса (17601) и перечисляем все процессы с идентификатором сеанса. Для pid=17601 мы получаем только одного дочернего элемента.

Однако реальное поддерево процесса выглядит так:

      $ pstree  -A  -s  -p   17601
systemd(1)---sshd(1109)---sshd(17601)---sshd(17652)---bash(17653)

$ ps -o user,pid,ppid,pgrp,sess,stat,cmd -p 1,1109,17601,17652,17653 --forest
USER       PID  PPID  PGRP  SESS STAT CMD
root         1     0     1     1 Ss   /sbin/init splash
root      1109     1  1109  1109 Ss   /usr/sbin/sshd -D
root     17601  1109 17601 17601 Ss    \_ sshd: vasya [priv]
vasya    17652 17601 17601 17601 S         \_ sshd: vasya@pts/17
vasya    17653 17652 17653 17653 Ss+           \_ -bash

Как вы можете видеть, у pid=17601 есть один дочерний элемент и один дочерний элемент (pid=17652,17653). Дело в том, что мы нашли дочерний элемент PID=17653. Метод из первого ответа этого не делает. Этот метод имеет недостаток, поскольку он использует два этапа. Однако преимущество в том, что если мы укажем PID, мы получим не только всех дочерних и всех дочерних элементов, но и всех родителей. То есть мы получаем все дерево для PID. Получаем целое дерево. Все процессы в списке являются либо родительскими, либо дочерними для соседа.

Я сделал аналогичный сценарий на основе вышеупомянутого Филиппа

pidlist() {
local thispid=$1
local fulllist=
local childlist=
childlist=$(ps --ppid $thispid -o pid h)
for pid in $childlist
do
  fulllist="$(pidlist $pid) $fulllist"
done
echo "$thispid $fulllist"
}

Это выводит все дочерние, внучатые и т. Д. Pids в формате с разделителями-пробелами. Это, в свою очередь, может быть подано в PS, как в

ps -p $ (pidlist pid)

Это все pids с ppid наконец, фильтруются те, которые заканчиваются на ppid нас интересует, тогда просто выбираем pid. Затем повторяется.

pidtree() {
  echo $1
  for p in $(ps -o pid=,ppid= | grep $1$ | cut -f1 -d' '); do
    pidtree $p
  done
}

pidtree $1

Я написал этот сценарий оболочки на основе ответа @y_159.

list_descendants ()
{
    local children=$( cat /proc/$1/task/*/children 2> /dev/null )
    for pid in $children
    do
        list_descendants $pid
    done
    echo $children
}

$ PID=`pidof gdm3`

$ ps f $PID $(list_descendants $PID)
    PID TTY      STAT   TIME COMMAND
   1492 ?        Ssl    0:00 /usr/sbin/gdm3
   1498 ?        Sl     0:00  \_ gdm-session-worker [pam/gdm-autologin]
   1531 tty2     Ssl+   0:00      \_ /usr/libexec/gdm-x-session --register-session ...
   1533 tty2     Sl+   27:25          \_ /usr/lib/xorg/Xorg vt2 -displayfd 3 ...
   1640 tty2     Sl+    0:00          \_ /usr/bin/startplasma-x11
   1745 ?        Ss     0:00              \_ /usr/bin/ssh-agent /usr/bin/

Если вам нужен еще более быстрый код, тогда...

$ awk -v PPID=`pidof gdm3` -v FPAT='[^ ]+|\\([^\\)]*\\)' '
    { a[$1] = $4 } END{ print PPID; desc(PPID) }
function desc( ppid,   i) {
    for ( i in a ) { if ( a[i] == ppid ) { print i; desc(i) }}
}
' /proc/[1-9]*/stat | xargs ps f
    PID TTY      STAT   TIME COMMAND
   1492 ?        Ssl    0:00 /usr/sbin/gdm3
   1498 ?        Sl     0:00  \_ gdm-session-worker [pam/gdm-autologin]
   1531 tty2     Ssl+   0:00      \_ /usr/libexec/gdm-x-session --register-session ...
   1533 tty2     Sl+   28:21          \_ /usr/lib/xorg/Xorg vt2 -displayfd 3 ...
   1640 tty2     Sl+    0:00          \_ /usr/bin/startplasma-x11
   1745 ?        Ss     0:00              \_ /usr/bin/ssh-agent /usr/bin/im-launch ...

Для всего процесса - pstree -a показать пользователем - pstree user

Другие вопросы по тегам