68

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?

2
  • 2
    Not an answer, but start with ps auxf.
    – jftuga
    Nov 30, 2011 at 21:34
  • 3
    @jtfuga This is in fact where I started, but this gives me all processes, which is exactly what I don't want.
    – kynan
    Dec 1, 2011 at 9:59

15 Answers 15

66

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
6
  • 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
    Jul 22, 2017 at 3:03
  • @TrueY do you know of a C API that can do that ? Thank you
    – Bionix1441
    Sep 20, 2017 at 7:45
  • Bionix No, sorry... I may read /proc/<PID> directories to build that tree.
    – TrueY
    Sep 20, 2017 at 13:44
  • 2
    Seems like this should be simple if ps had a filter flag option just to filter to all descendants of a PID. Sep 17, 2018 at 4:33
  • Regarding your last awk oneliner, I wonder if it has to be that complex?
    – x-yuri
    Mar 28, 2020 at 15:54
35
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/

2
  • 13
    Thanks, 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
    Dec 1, 2011 at 10:00
  • 4
    I 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:]]*" Jun 17, 2014 at 23:36
17

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
)
3
  • 1
    This 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. Aug 16, 2016 at 18:29
  • 1
    I 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
    Jul 22, 2017 at 3:01
  • Thanks you for the inspiration. I made a POSIX compliant version of it: superuser.com/a/1721354/114255
    – Kay
    May 16, 2022 at 21:47
5

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
1
  • The -H option is used to display a process tree or "process hierarchy (forest)" Aug 31, 2016 at 16:04
4

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

3
  • 1
    There 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? Jun 17, 2015 at 10:34
  • 1
    I used recursion and added some explanation. Thought it might help someone. It does exactly what the title asks for.
    – Merijn
    Jun 17, 2015 at 12:10
  • I like the use of pgrep here, since ps can differ among unix variants. Aug 2, 2016 at 11:08
3

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
}
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 '' "$@"
}
1
  • Simple and splendid. Feb 13, 2020 at 18:52
2

You can get the pids of all child processes of a given parent process <pid> by reading the /proc/<pid>/task/<tid>/children entry.

This file contains the pids of first-level child processes. Recursively you can see for children too.

For more information head over to https://lwn.net/Articles/475688/

1
  • This is unreliable, as per man7.org/linux/man-pages/man5/proc.5.html: "[...] reliably provides a list of children only if all of the child processes are stopped or frozen." As this isn't the case, the underlying iterator generating th3 pseudo file contents can return arbitrary subsets of the real children set.
    – TheDiveO
    Jan 6 at 16:21
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
1

If we want to see all the children and all subchildren of a process the first answer doesnt work. The first answer assumes that all children and subchildren have the same session id. It is wrong.

An example: lets take pid=17601

  $ ps --forest -o pid,ppid,pgrp,sess,tty,stat,time,cmd -g $(ps -o sid= -p 17601)
  PID  PPID  PGRP  SESS TT       STAT     TIME CMD
17601  1109 17601 17601 ?        Ss   00:00:00 sshd: vasya [priv]
17652 17601 17601 17601 ?        S    00:00:00  \_ sshd: vasya@pts/17

so we take the process with pid=17601, find its session id(17601) and list all the processes with the session id. For pid=17601 we get only one child.

However the real process subtree looks like this:

$ pstree  -A  -s  -p   17601
systemd(1)---sshd(1109)---sshd(17601)---sshd(17652)---bash(17653)

$ ps -o user,pid,ppid,pgrp,sess,stat,cmd -p 1,1109,17601,17652,17653 --forest
USER       PID  PPID  PGRP  SESS STAT CMD
root         1     0     1     1 Ss   /sbin/init splash
root      1109     1  1109  1109 Ss   /usr/sbin/sshd -D
root     17601  1109 17601 17601 Ss    \_ sshd: vasya [priv]
vasya    17652 17601 17601 17601 S         \_ sshd: vasya@pts/17
vasya    17653 17652 17653 17653 Ss+           \_ -bash

As you can see the pid=17601 has one child and one subchild (pid=17652,17653).The point is we have found the subchild PID=17653. The method from the first answer doesn't do that. This method has disadvantage because it uses two steps. However the advantage is that is we specify a PID then we get not only all the children and all subchildren but also all the parents. That is we get all the tree for the PID. We get the whole tree. All the processes in the list are either a parent or a child for a neighbour.

1

@xzfc's solution (https://superuser.com/a/784102/114255) works. However it uses some shell-specific features (e.g., shopt, associative array). It also has special handling for zsh.

I adapted it to make it POSIX compliant:

pidtree() (
    # lines of "parent child"
    _PID_GRAPH="$(ps -e -o ppid= -o pid=)"

    _walk() {
        echo $1
        # iterate direct children of "$1"
        # awk: if the parent field matches $1, then print child field
        echo "$_PID_GRAPH" | awk -F' ' "\$1==\"$1\" {print \$2;}" | while read child_pid; do
            _walk "$child_pid"
        done
    }

    for i in "$@"; do _walk $i; done
)

0

This gets all pids with the ppid last, filters on those ending with the ppid we're interested in, then just selects the pid. Then recurses.

pidtree() {
  echo $1
  for p in $(ps -o pid=,ppid= | grep $1$ | cut -f1 -d' '); do
    pidtree $p
  done
}

pidtree $1
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*)
0

I have writed this shell script based on @y_159 answer.

list_descendants ()
{
    local children=$( cat /proc/$1/task/*/children 2> /dev/null )
    for pid in $children
    do
        list_descendants $pid
    done
    echo $children
}

$ PID=`pidof gdm3`

$ ps f $PID $(list_descendants $PID)
    PID TTY      STAT   TIME COMMAND
   1492 ?        Ssl    0:00 /usr/sbin/gdm3
   1498 ?        Sl     0:00  \_ gdm-session-worker [pam/gdm-autologin]
   1531 tty2     Ssl+   0:00      \_ /usr/libexec/gdm-x-session --register-session ...
   1533 tty2     Sl+   27:25          \_ /usr/lib/xorg/Xorg vt2 -displayfd 3 ...
   1640 tty2     Sl+    0:00          \_ /usr/bin/startplasma-x11
   1745 ?        Ss     0:00              \_ /usr/bin/ssh-agent /usr/bin/

If you need even faster code then ...

$ awk -v PPID=`pidof gdm3` -v FPAT='[^ ]+|\\([^\\)]*\\)' '
    { a[$1] = $4 } END{ print PPID; desc(PPID) }
function desc( ppid,   i) {
    for ( i in a ) { if ( a[i] == ppid ) { print i; desc(i) }}
}
' /proc/[1-9]*/stat | xargs ps f
    PID TTY      STAT   TIME COMMAND
   1492 ?        Ssl    0:00 /usr/sbin/gdm3
   1498 ?        Sl     0:00  \_ gdm-session-worker [pam/gdm-autologin]
   1531 tty2     Ssl+   0:00      \_ /usr/libexec/gdm-x-session --register-session ...
   1533 tty2     Sl+   28:21          \_ /usr/lib/xorg/Xorg vt2 -displayfd 3 ...
   1640 tty2     Sl+    0:00          \_ /usr/bin/startplasma-x11
   1745 ?        Ss     0:00              \_ /usr/bin/ssh-agent /usr/bin/im-launch ...
-2
  • for all processes: pstree -a
  • show by user: pstree user
1
  • 2
    Please explain some more why this answers the question. Mar 18, 2016 at 7:12

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .