72

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
  • 3,586

15 Answers15

72

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
  • 850
  • 7
  • 8
36
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/

codeforester
  • 164
  • 1
  • 1
  • 8
Eroen
  • 6,561
19

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
  • 521
7

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
  • 211
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

Merijn
  • 41
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 '' "$@"
}
Ole Tange
  • 5,099
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/

y_159
  • 121
2

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.

Alex
  • 150
2

@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 &quot;$1&quot;
    # awk: if the parent field matches $1, then print child field
    echo &quot;$_PID_GRAPH&quot; | awk -F' ' &quot;\$1==\&quot;$1\&quot; {print \$2;}&quot; | while read child_pid; do
        _walk &quot;$child_pid&quot;
    done
}

for i in &quot;$@&quot;; do _walk $i; done

)

Kay
  • 1,331
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
  • 289
  • 3
  • 9
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
BenTaylor
  • 101
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 ...
mug896
  • 199
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*)
kynan
  • 3,586
-3
  • for all processes: pstree -a
  • show by user: pstree user
kynan
  • 3,586