ps: How can i recursively get all child process for a given pid

44

19

How can I get the entire process tree spawned by a given process displayed as a tree and only that tree i.e. no other processes?

The output could e.g. look like

 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

I couldn't get the desired result purely with parameters to ps.

The following gives the desired result but seems a bit involved:

#!/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`

Does anyone have an easier solution?

kynan

Posted 2011-11-30T21:25:19.460

Reputation: 2 646

Not an answer, but start with ps auxf. – jftuga – 2011-11-30T21:34:28.753

3@jtfuga This is in fact where I started, but this gives me all processes, which is exactly what I don't want. – kynan – 2011-12-01T09:59:39.987

Answers

38

The pstree is a very good solution, but it is a little bit reticent. I use ps --forest instead. But not for a PID (-p) because it prints only the specific process, but for the session (-g). It can print out any information ps can print in a fancy ASCII art tree defining the -o option.

So my suggestion for this problem:

ps --forest -o pid,tty,stat,time,cmd -g 2795

If the process is not a session leader, then a little bit more trick has to be applied:

ps --forest -o pid,tty,stat,time,cmd -g $(ps -o sid= -p 2795)

This gets the session id (SID) of the current process first and then call ps again with that sid.

If the column headers are not needed add a '=' after each column definition in '-o' options, like:

ps --forest -o pid=,tty=,stat=,time=,cmd= -g $(ps -o sid= -p 2795)

An example run and the result:

$ 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

Unfortunately this does not work for screen as it sets the sid for each child screen and all grandchild bash.

To get all the processes spawned by a process the whole tree needs to be built. I used for that. At first it builds a hash array to contain all PID => ,child,child... . At the end it calls a recursive function to extract all the child processes of a given process. The result is passed to another ps to format the result. The actual PID has to be written as an argument to instead of <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)}')

For a SCREEN process (pid=8041) the example output looks like this:

  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

TrueY

Posted 2011-11-30T21:25:19.460

Reputation: 511

The issue with this (I know, it's an old answer) is the --forest option only works on Linux (or other gnu based "ps" commands). Solaris, and MacOS don't like it. – Armand – 2017-07-22T03:03:22.470

@TrueY do you know of a C API that can do that ? Thank you – Bionix1441 – 2017-09-20T07:45:01.770

Bionix No, sorry... I may read /proc/<PID> directories to build that tree. – TrueY – 2017-09-20T13:44:49.177

1Seems like this should be simple if ps had a filter flag option just to filter to all descendants of a PID. – CMCDragonkai – 2018-09-17T04:33:43.430

28

pstree ${pid}

where ${pid} is the pid of the parent process.

On Gentoo Linux, pstree is in the package "psmisc," apparently located at http://psmisc.sourceforge.net/

Eroen

Posted 2011-11-30T21:25:19.460

Reputation: 5 615

9Thanks, should have mentioned that I had looked at pstree, but missed a more verbose output format. However, pstree -p <pid> at least print the pids which is reasonably close. – kynan – 2011-12-01T10:00:56.163

2I have this problem too, I need to gather all child pids recursively but I need only the pids, so I would have to sed all that.. mmm this works :) pstree -pn 4008 |grep -o "([[:digit:]]*)" |grep -o "[[:digit:]]*" – Aquarius Power – 2014-06-17T23:36:57.657

12

Here is my version that runs instantly (because ps executed only once). Works in bash and 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
)

xzfc

Posted 2011-11-30T21:25:19.460

Reputation: 431

1This also prints out the pid of the current process, which you may or may not want. I didn't want the current process listed (just descendants of the current process), so I changed the script by moving echo $1 inside the first for loop and changing it to echo $i. – Mark Lakata – 2016-08-16T18:29:39.690

1I was just about to write such a function when I found this. This is the only solution that seems to work on MacOS, Linux, and Solaris. Great work. Love that you rely on a single run of ps to get the job done in memory. – Armand – 2017-07-22T03:01:30.817

3

ps -H -g "$pid" -o comm

doesn't add a tree per se, it is just the list of processes.

gives for example

COMMAND
bash
  nvim
    python

edi9999

Posted 2011-11-30T21:25:19.460

Reputation: 109

The -H option is used to display a process tree or "process hierarchy (forest)" – Anthony Geoghegan – 2016-08-31T16:04:51.340

3

I've created a small bash script to create a list pid's of a parent's child process(es). Recursively till it finds the last child process which does not have any childs. It does not give you a tree view. It just lists all pid's.

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

first argument of list_offspring is the parent pid

Merijn

Posted 2011-11-30T21:25:19.460

Reputation: 31

1There are already several other answers that give a bash script that doesn't print an actual tree, including one in the question itself. What advantages does your (partial) answer have over the existing (partial) answers? – David Richerby – 2015-06-17T10:34:00.607

I used recursion and added some explanation. Thought it might help someone. It does exactly what the title asks for. – Merijn – 2015-06-17T12:10:41.593

I like the use of pgrep here, since ps can differ among unix variants. – John Szakmeister – 2016-08-02T11:08:57.740

2

This gives only the pid+children pids each on one line which is useful for further processing.

pidtree() {
    for _pid in "$@"; do
        echo $_pid
        pidtree `ps --ppid $_pid -o pid h`
    done
}

With nicer tree layout:

_pidtree() {
    local p="$1"
    shift
    for _pid in "$@"; do
        echo "$p$_pid"
        _pidtree " $p" `ps --ppid $_pid -o pid h`
    done
}
pidtree() {
    _pidtree '' "$@"
}

Ole Tange

Posted 2011-11-30T21:25:19.460

Reputation: 3 034

Simple and splendid. – Irfan Latif – 2020-02-13T18:52:21.173

2

I have been working to find a solution to the exact same problem. Bascially, ps manpage does not document any option allowing to do what we want with a single command. Conclusion: a script is needed.

I came up with a script very similar to yours. I pasted it in my ~/.bashrc so I can use it from any shell.

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
}

Philippe A.

Posted 2011-11-30T21:25:19.460

Reputation: 222

1

On way on the command-line:

ps -o time,pid,ppid,cmd --forest -g -p $(pgrep -x bash)

it outputs:

    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

more elegant way of that way is to define a function in .bashrc:

function subps()                                                                                    
{                                                                                                   
    process=$(pgrep -x $1)                                                                                                                                                                     
    ps -o time,pid,ppid,cmd --forest -g -p $process                                                 
}    

then on the command-line run:

subps bash

Shakiba Moshiri

Posted 2011-11-30T21:25:19.460

Reputation: 121

0

I made a similar script based on Philippe's above

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

This outputs all the child, grandchild, etc. pids in space-delimited format. This can, in turn, be fed to ps, as in

ps -p $(pidlist pid)

kylehutson

Posted 2011-11-30T21:25:19.460

Reputation: 1

-2

for all process - pstree -a show by user - pstree user

Andy Wong

Posted 2011-11-30T21:25:19.460

Reputation: 101

2Please explain some more why this answers the question. – user 99572 is fine – 2016-03-18T07:12:31.257