pgrep -P, but for grandchildren not just children

前端 未结 6 2116
猫巷女王i
猫巷女王i 2021-01-07 05:18

I am using:

pgrep -P $$

to get the child pids of $$. But I actually want a list of grandchildren and great grandchild too.

How do I

相关标签:
6条回答
  • 2021-01-07 05:51

    If pgrep doesn't do what you want, you can always use ps directly. Options will be somewhat platform-dependent.

    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 }'
    

    The variable names are not orthogonal -- parent collects the processes which are pid or one of its children as keys, i.e. the "interesting" parents, and child contains the parent of each process, with the process as the key and the parent as the value.

    0 讨论(0)
  • 2021-01-07 05:58

    The code below will print the PIDs of the current process and all its descendants. It uses a Bash array as a queue to implement a breadth-first search of the process tree.

    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
    
    0 讨论(0)
  • 2021-01-07 06:09

    Using nothing but bash builtins (not even ps or pgrep!):

    #!/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 "$@"
    

    On my local system, time all_children_of 1 >/dev/null (invoking the function in an already-running shell) clocks in the neighborhood of 0.018s -- typically, 0.013s for the collect_children stage (the one-time action of reading the process tree), and 0.05s for the recursive walk of that tree triggered by the initial call of _all_children_of.

    Prior timings were testing only the time needed for the walk, discarding the time needed for the scan.

    0 讨论(0)
  • 2021-01-07 06:09

    Probably a simple loop would do it:

    # 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
    
    0 讨论(0)
  • I ended up doing this with node.js and 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);
          });
    
        };
    
    0 讨论(0)
  • 2021-01-07 06:18

    I've already posted an attempted solution. It's short and effective, and seems in line with the OP's question, so I'll leave it as it is. However, it has some performance and portability problems that mean it's not a good general solution. This code attempts to fix the problems:

    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[@]}"
    

    The basic approach of using a breadth-first search to build up the tree has been retained, but the essential information about processes is obtained with a single (POSIX-compliant) run of ps. pgrep is no longer used because it is not in POSIX and it could be run many times. Also, a very inefficient way of removing items from the queue (copy all but one element of it) has been replaced with manipulation of an index variable.

    Average (real) run time is 0.050s when run on pid 0 on my oldish Linux system with around 400 processes.

    I've only tested it on Linux, but it only uses Bash 3 features and POSIX-compliant features of ps so it should work on other systems too.

    0 讨论(0)
提交回复
热议问题