优雅地获取后代进程列表


23

我想获得所有从中降级的过程的列表(例如,子代,孙子代等)$pid。这是我想出的最简单的方法:

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

是否有任何命令或更简单的方法来获取所有后代进程的完整列表?


您是否一并需要它们?您正在使用该输出做什么?我感觉这是一个xy问题,您问的是错误的问题。
jordanm

只要格式干净,我就不在乎(即我不在乎'\n'定界与' '定界)。实际的用例是:a)我纯粹出于受虐狂而写的守护进程脚本(具体来说,“停止”功能必须处理守护进程生成的任何进程树);和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 2013年

1
另一个用例:整个进程树上的renice / ionice正在消耗过多资源,例如大型并行构建。
猎豹

Answers:


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)。但是,如果命令名称包含带括号的数字,则所有选择均无效。到目前为止,只有文字处理才能带您进入。

所以我也认为过程组是要走的路。如果您想在自己的进程组中运行某个进程,则可以使用Debian软件包“ daemontools”中的“ pgrphack”工具:

pgrphack my_command args

或者,您可以再次转向Perl:

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

唯一需要注意的是,进程组不会嵌套,因此,如果某个进程正在创建自己的进程组,则其子进程将不再位于您创建的组中。


子进程是任意的,它们本身可以使用也可以不使用进程组(我不能承担任何责任)。但是,您的回答最接近Linux可以实现的效果,因此我会接受。谢谢。
STenyaK

这非常有用!
Michal Gallovic '16

pstree管道还将包含线程ID,即$ pid已启动的线程的ID。
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
}

这将是唯一值得注意的是,这将现代贝壳工作(bashzshfish,甚至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也会打印这些ID)。
maxschlepzig

@maxschlepzig注意ffmpeg使用线程非常有问题。但是,从快速观察中可以看出,线程似乎在花括号中带有其名称{ }
吉普赛Spellweaver

1

还有正确性的问题。幼稚地解析的输出pstree是有问题的,原因如下:

  • pstree显示PID 线程ID(名称用大括号显示)
  • 命令名称可能包含花括号,括号中的数字,从而无法进行可靠的解析

如果您已psutil安装Python和软件包,则可以使用此代码段列出所有后代进程:

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

(例如,psutil软件包是作为tracer命令的依赖项安装的,该命令在Fedora / CentOS上可用。)

另外,您可以在bourne shell中进行流程树的广度优先遍历:

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

为了计算pid的传递闭包,可以省略尾部。

请注意,以上代码不使用递归,也可以在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

因为我们为每个PID节省了一个fork / exec,并且pgrep在每个调用中执行了一些额外的工作,所以这效率更高。


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

0

在您的两个(看似非常人为的)用例中,为什么要杀死一些不幸的流程的子流程?您比哪个过程更了解孩子的生命或死亡?在我看来,这似乎是糟糕的设计。一个过程应自行清理。

如果您确实确实了解得更多,那么您应该分叉这些子流程,并且“守护进程”显然太愚蠢而无法信任fork(2)

您应该避免保留子进程列表或在进程树中进行漫游,例如,按照@Gilles的建议,将子进程放在单独的进程组中。

无论如何,我怀疑您的守护进程最好比创建一个子子子进程的深树更好地创建一个工作线程池(该线程必须随其包含的进程一起死亡),然后再清理该子树。 。


2
这两个用例都在连续集成/测试环境中使用,因此它们必须处理子进程中存在错误的可能性。该错误可能表明自己无法正确关闭自己或他们的孩子,因此我需要一种方法来确保在最坏的情况下可以将它们全部关闭。
STenyaK

1
在那种情况下,我和@Gilles和@Jander在一起。流程组是最好的方法。
AnotherSmellyGeek 2013年

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
由于这是bash,我有种感觉,可以使用设置IFS和使用数组("${array[*]}“)替换用于将PID与定界符结合起来的代码
。– muru
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.