【问题标题】:pgrep -P, but for grandchildren not just childrenpgrep -P,但对于孙子,不仅仅是孩子
【发布时间】:2018-09-26 02:00:30
【问题描述】:

我正在使用:

pgrep -P $$

获取 $$ 的子 pid。但实际上我也想要一份孙子和曾孙的名单。

我该怎么做呢?例如,使用常规编程语言,我们可以使用递归来做到这一点,但是使用 bash?也许使用 bash 函数?

【问题讨论】:

标签: bash shell recursion grep


【解决方案1】:

我已经发布了attempted solution。它简短而有效,似乎符合 OP 的问题,所以我将保持原样。但是,它存在一些性能和可移植性问题,这意味着它不是一个好的通用解决方案。此代码尝试解决问题:

top_pid=$1

# Make a list of all process pids and their parent pids
ps_output=$(ps -e -o pid= -o ppid=)

# Populate a sparse array mapping pids to (string) lists of child pids
children_of=()
while read -r pid ppid ; do
    [[ -n $pid && pid -ne ppid ]] && children_of[ppid]+=" $pid"
done <<< "$ps_output"

# Add children to the list of pids until all descendants are found
pids=( "$top_pid" )
unproc_idx=0    # Index of first process whose children have not been added
while (( ${#pids[@]} > unproc_idx )) ; do
    pid=${pids[unproc_idx++]}       # Get first unprocessed, and advance
    pids+=( ${children_of[pid]-} )  # Add child pids (ignore ShellCheck)
done

# Do something with the list of pids (here, just print them)
printf '%s\n' "${pids[@]}"

使用广度优先搜索来构建树的基本方法已被保留,但有关进程的基本信息是通过ps 的单个(POSIX 兼容)运行获得的。 pgrep 不再使用,因为它不在 POSIX 中并且可以多次运行。此外,从队列中删除项目的一种非常低效的方法(复制除一个元素之外的所有元素)已被索引变量的操作所取代。

在具有大约 400 个进程的旧 Linux 系统上以 pid 0 运行时,平均(实际)运行时间为 0.050 秒。

我只在 Linux 上测试过它,但它只使用了 Bash 3 功能和ps 的符合 POSIX 标准的功能,因此它也应该可以在其他系统上运行。

【讨论】:

  • 这对我来说看起来不错。我自己写这个我可能会直接从ps 流入while read 循环(使用进程替换),但我也可以理解为什么最好不要走这条路(如果你担心内部一致性, 如果来自ps 的阻塞写入导致它在更长的时间内传播对进程树的检查)。
【解决方案2】:

只使用 bash 内置函数(甚至不使用 pspgrep!):

#!/usr/bin/env bash

collect_children() {
  # format of /proc/[pid]/stat file; group 1 is PID, group 2 is its parent
  stat_re='^([[:digit:]]+) [(].*[)] [[:alpha:]] ([[:digit:]]+) '

  # read process tree into a bash array
  declare -g children=( )              # map each PID to a string listing its children
  for f in /proc/[[:digit:]]*/stat; do # forcing initial digit skips /proc/net/stat
    read -r line <"$f" && [[ $line =~ $stat_re ]] || continue
    children[${BASH_REMATCH[2]}]+="${BASH_REMATCH[1]} "
  done
}

# run a fresh collection, then walk the tree
all_children_of() { collect_children; _all_children_of "$@"; }

_all_children_of() {
  local -a immediate_children
  local child
  read -r -a immediate_children <<<"${children[$1]}"
  for child in "${immediate_children[@]}"; do
    echo "$child"
    _all_children_of "$child"
  done
}

all_children_of "$@"

在我的本地系统上,time all_children_of 1 &gt;/dev/null(在已经运行的 shell 中调用函数)时钟在 0.018 秒左右——通常,collect_children 阶段为 0.013 秒(读取进程树),以及由_all_children_of 的初始调用触发的该树的递归遍历为 0.05 秒。

之前的计时只测试步行所需的时间,丢弃了扫描所需的时间。

【讨论】:

  • 这需要 /proc 文件系统,但它在 Linux 之外并不广泛使用。
  • 确实——我以为这被标记为 Linux,但似乎是错误的。
  • @CharlesDuffy,这是一个非常聪明的解决方案。我最初的解决方案尝试要慢得多。我进行了第二次尝试,这应该比这要快得多,而且更便携。我很想听听它在您的系统上的表现。
【解决方案3】:

下面的代码将打印当前进程及其所有后代的 PID。它使用 Bash 数组作为队列来实现进程树的breadth-first search

unprocessed_pids=( $$ )
while (( ${#unprocessed_pids[@]} > 0 )) ; do
    pid=${unprocessed_pids[0]}                      # Get first elem.
    echo "$pid"
    unprocessed_pids=( "${unprocessed_pids[@]:1}" ) # Remove first elem.
    unprocessed_pids+=( $(pgrep -P $pid) )          # Add child pids
done

【讨论】:

    【解决方案4】:

    可能一个简单的循环就可以做到:

    # set a value for pid here
    printf 'Children of %s:\n' $pid
    for child in $(pgrep -P $pid); do
        printf 'Children of %s:\n' $child
        pgrep -P $child
    done
    

    【讨论】:

    • 似乎不是递归的?好像只给孙子,不给曾孙?我正在寻找一直向下钻取。
    • 很容易添加另一层,但(像往常一样)Charles Duffy 有更好的答案!
    【解决方案5】:

    如果pgrep 不符合您的要求,您始终可以直接使用ps。选项在某种程度上取决于平台。

    ps -o ppid,pid |
    awk -v pid=$$ 'BEGIN { parent[pid] = 1 }  # collect interesting parents
        { child[$2] = $1 }  # collect parents of all processes
        $1 == pid { parent[$2] = 1 }
        END { for (p in child)
            if (parent[child[p]])
              print p }'
    

    变量名不是正交的——parent 收集进程是 pid 或其子进程之一作为键,即“有趣的”父进程,child 包含每个进程的父进程,其中进程作为键,父进程作为值。

    【讨论】:

    • 嗯。尝试对此进行测试,但是当我将其设为 ps axe -o ppid,pid 并告诉 awk 从 PID 1 而不是 $$ (-v pid=1) 开始时,我什至在我的整棵树附近都没有得到任何东西。
    • 我理解这个问题是指 init 进程的直接后代,而不是完整的树。仔细阅读,我错过了递归要求。我会看看我是否能找到时间来解决这个问题。
    【解决方案6】:

    我最终用 node.js 和 bash 做这个:

     const async = require('async');
     const cp = require('child_process');
    
     export const getChildPids = (pid: number, cb: EVCb<Array<string>>) => {
    
          const pidList: Array<string> = [];
    
          const getMoreData = (pid: string, cb: EVCb<null>) => {
    
            const k = cp.spawn('bash');
            const cmd = `pgrep -P ${pid}`;
            k.stderr.pipe(process.stderr);
            k.stdin.end(cmd);
            let stdout = '';
            k.stdout.on('data', d => {
              stdout += String(d || '').trim();
            });
    
            k.once('exit', code => {
    
              if (code > 0) {
                log.warning('The following command exited with non-zero code:', code, cmd);
              }
    
              const list = String(stdout).split(/\s+/).map(v => String(v || '').trim()).filter(Boolean);
    
              if (list.length < 1) {
                return cb(null);
              }
    
              for (let v of list) {
                pidList.push(v);
              }
    
              async.eachLimit(list, 3, getMoreData, cb);
    
            });
          };
    
          getMoreData(String(pid), err => {
            cb(err, pidList);
          });
    
        };
    

    【讨论】:

      猜你喜欢
      • 2020-02-04
      • 1970-01-01
      • 1970-01-01
      • 2014-10-18
      • 1970-01-01
      • 2012-02-13
      • 2019-02-08
      • 2017-11-10
      • 1970-01-01
      相关资源
      最近更新 更多