34

I would like to get a list of all the processes that descend (e.g. children, grand-children, etc) from $pid. This is the simplest way I've come up with:

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

Is there any command, or any simpler way to get the full list of all descendant processes?

perror
  • 3,239
  • 7
  • 33
  • 45
STenyaK
  • 745
  • Is there a reason you need them all on one line? What are you doing with that output? I have a feeling that this is an xy problem, and you are asking the wrong question. – jordanm Mar 12 '13 at 17:03
  • I don't care about the format as long as it's clean (i.e. I don't care about '\n' delimited vs. ' ' delimited). Practical use case is: a) a daemonizer script I wrote out of pure masochism (specifically, the "stop" functionality has to deal with whatever tree of processes the daemonized process spawned); and b) a timeout script that will kill whatever the timed-out process managed to create. – STenyaK Mar 12 '13 at 21:06
  • 2
    @STenyaK Your use cases make me think you're looking for process groups and a negative argument to kill. See http://unix.stackexchange.com/questions/9480/how-to-suspend-and-resume-proccesses-like-bash-does/9482#9482, http://unix.stackexchange.com/questions/50555/kill-many-instances-of-a-running-process-with-one-command/51869#51869 – Gilles 'SO- stop being evil' Mar 12 '13 at 21:51
  • @Gilles using ps ax -opid,ppid,pgrp,cmd I see there are many processes that share the same pgrp as the exact subtree I want to kill. (Additionally, I can't see the setpgrp program listed anywhere in debian stable packages: http://packages.debian.org/search?searchon=contents&keywords=setpgrp&mode=filename&suite=stable&arch=any ) – STenyaK Mar 13 '13 at 07:42
  • 1
    Another use case: renice/ionice on a whole process tree that's eating too many resources, e.g. a large parallel build. – Cheetah Sep 25 '13 at 04:02
  • The pstree example also lists the thread ids when the process has spawned some threads. – maxschlepzig Jan 21 '17 at 09:10
  • See also https://unix.stackexchange.com/questions/264522/how-can-i-show-a-terminal-shells-process-tree-including-children – Vadzim Sep 02 '21 at 18:30
  • this is answered here – y_159 Feb 19 '22 at 08:20

11 Answers11

26

The following is somewhat simpler, and has the added advantage of ignoring numbers in the command names:

pstree -p $pid | grep -o '([0-9]\+)' | grep -o '[0-9]\+'

Or with Perl:

pstree -p $pid | perl -ne 'print "$1\n" while /\((\d+)\)/g'

We're looking for numbers within parentheses so that we don't, for example, give 2 as a child process when we run across gif2png(3012). But if the command name contains a parenthesized number, all bets are off. There's only so far text processing can take you.

So I also think that process groups are the way to go. If you'd like to have a process run in its own process group, you can use the 'pgrphack' tool from the Debian package 'daemontools':

pgrphack my_command args

Or you could again turn to Perl:

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

The only caveat here is that process groups do not nest, so if some process is creating its own process groups, its subprocesses will no longer be in the group that you created.

Jander
  • 16,682
10
descendent_pids() {
    pids=$(pgrep -P $1)
    echo $pids
    for pid in $pids; do
        descendent_pids $pid
    done
}
  • It would be only worth noting that this will work on modern shells (bash, zsh, fish, and even ksh 99), but might not work on older shells, e.g. ksh 88 – grochmal Jul 29 '16 at 23:34
  • @grochmal, see my answer below for a traversal solution that does work in ksh-88. – maxschlepzig Jan 21 '17 at 09:06
7

There is also the issue of correctness. Naively parsing the output of pstree is problematic for several reasons:

  • pstree displays PIDs and the ids of threads (names are shown in curly braces)
  • a command name might contain curly braces, numbers in parentheses that make reliable parsing impossible

If you have Python and the psutil package installed you can use this snippet to list all descendant processes:

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

(The psutil package is e.g. installed as a dependency of the tracer command which is available on Fedora/CentOS.)

Alternatively, you can do an breadth-first traversal of the process tree in a bourne shell:

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

For computing the transitive-closure of a pid, the tail part can be omitted.

Note that the above doesn't use recursion and also runs in ksh-88.

On Linux, one can eliminate the pgrep call and instead read the information from /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

This is more efficient because we save one fork/exec for each PID and pgrep does some additional work in each call.

maxschlepzig
  • 57,532
1

This Linux version needs /proc and ps only. It's adapted from the last piece of @maxschlepzig's excellent answer. This version reads /proc directly from the shell instead of spawning a sub-process in a loop. It's a bit faster and arguably slightly more elegant, as this thread title requests.

#!/bin/dash

# Print all descendant pids of process pid $1
# adapted from https://unix.stackexchange.com/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
stepse
  • 149
1

In each of your two (seemingly very artificial) use cases, why do you want to kill some unfortunate process's sub-processes? How do you know better than a process when its children should live or die? This seems like poor design to me; a process should clean up after itself.

If you really do know better, then you should be forking these sub-processes, and the 'daemonized process' is apparently too dumb to be trusted to fork(2).

You should avoid keeping lists of child processes or grovelling through the process tree, eg by putting the child processes in a separate process group as suggested by @Gilles.

In any case, I suspect that your daemonized process would be better off creating a worker thread pool (which necessarily dies along with its containing process) than a deep tree of sub-sub-sub-processes, which something somewhere then has to clean up.

  • 2
    Both use cases are used in a continuous integration/testing environment, so they have to deal with the possibility of a bug existing in the child process/es. This bug may manifest itself as inability to properly shutdown themselves or their children, so I need a way to ensure that I can close them all in the worst case. – STenyaK Jul 17 '13 at 11:55
  • 1
    In that case, I'm with @Gilles and @Jander; process groups are the best way. – AnotherSmellyGeek Sep 13 '13 at 13:58
  • For anyone coming across this in 2022, these days the most sure-fire way to clean up all child processes is to use a PID namespace and then kill all processes in the namespace (and the easiest way to do that is to use something like a Docker or Podman container, and kill the whole container with --force). Killing by process group does not work if one of the child processes in the group decides to fork off into its own process group. – Brandon Jun 28 '22 at 00:21
1

The shortest version I have found that also deals correctly with commands like pop3d:

pstree -p $pid | perl -ne 's/\((\d+)\)/print " $1"/ge'

It deals wrongly if you have commands that have weird names like: my(23)prog.

Ole Tange
  • 35,514
  • 1
    This doesn't work for commands that are running some thread (because pstree prints those IDs, as well). – maxschlepzig Jan 21 '17 at 09:08
  • @maxschlepzig Noticed that very problem with ffmpeg using threads. Though, from quick observations, it seems that the threads are given with their name inside curly braces, { }. – Chindraba Feb 17 '19 at 23:39
0

Here's a pgrep wrapper script which lets you use pgrep and get all descendants at the same time.

~/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

Invoke the same way you'd invoke normal pgrep, such as pgrep_recursive -U $USER java to find all Java processes and sub-processes from the current user.

muru
  • 72,889
zeroimpl
  • 101
  • 1
    Since this is bash, I have a feeling the code used for joining the PIDs with the delimiter could be replaced with setting IFS and using arrays ( "${array[*]}"). – muru Apr 10 '17 at 01:22
0

I built a (Linux/procfs) tool called Procpath to query the process tree using JSONPath.

To get all descendant process' comma-separated PIDs of a non-root process (i.e. for PID=1 use simpler query ..stat.pid) it's:

$ procpath query -d, "..children[?(@.stat.pid == 15849)]..pid"
15849,16000,16039,16072,16109,16199
saaj
  • 2,438
0

Yet another non-elegent script, using loop instead of recursion.

It only uses ps to recursively list all descendant process IDs.

It calls ps --ppid $PID -o pid= to output direct children PIDs of specified $PID, the format specifier -o pid= causes the output to be PIDs only.

#!/bin/sh

if [ -z "$1" ]; then echo USAGE: $0 PID exit 0 fi

get_children() { ps --ppid $1 -o pid= }

children="$1" output="" while [ -n "$children" ]; do new_children= for id in $children; do result=$(get_children $id) if [ -n "$result" ]; then output="${output:+${output} }$result" new_children="${new_children:+${new_children} }$result" fi done children="$new_children" done #printf "%s\n" $output echo $output

zerox
  • 101
0

The following ksh88 code will return a list of the PIDs of all descendents of a particular PID. The ps invocation is for HP-UX 11.00, but something similar should work for any UNIX/Linux whose ps command can provide the PID and PPID fields.

It is divided into a non-recursive and recursive part, which allows ps to be run only once.

#-----------------------------------------------------------------------
# find_children_r()
# -----------------
# This is the recursive part which repeatedly finds all children for a
# particular set of PIDs and then calls itself to find the children of
# the children...
#-----------------------------------------------------------------------
export UNIX95=1 # needed for the "-o" flag to "ps"
function find_children_r {
    typeset parents="$1" ps_out="$2" found res tmp pid ppid args p
print -- &quot;$ps_out&quot;| while read pid ppid args; do
    # skip the header line
    [[ $pid = &quot;PID&quot; ]] &amp;&amp; continue

    # skip ps command
    [[ $args = ps*pid,ppid,args ]] &amp;&amp; continue

    for p in $parents; do
        if (( ppid == p )) ; then
            found=&quot;${found:+${found} }$pid&quot;
        fi
    done
done

if [[ -z &quot;$found&quot; ]]; then return 1; fi

if tmp=&quot;$(find_children_r &quot;$found&quot; &quot;$ps_out&quot;)&quot;; then
    print -- &quot;${found} $tmp&quot;
else
    print -- &quot;${found}&quot;
fi

return 0

}

#-----------------------------------------------------------------------

find_children()

---------------

The non-recursive part that runs "ps" once and then passes the output

to the recursive part

#----------------------------------------------------------------------- function find_children { typeset top_pid="$1" ps_out res

ps_out=&quot;$(ps -efo pid,ppid,args)&quot;
res=&quot;$(find_children_r &quot;$top_pid&quot; &quot;$ps_out&quot;)&quot;

print -- &quot;$res&quot;

}

AdminBee
  • 22,803
-1

ps auxf will show you a full view of the process tree.

Kusalananda
  • 333,661