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)
Это все
pid
s с
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