How do I find the top-level parent PID of a given process using bash?

Bash can definitely do recursion.

You can retrieve the fourth field from the stat file without using the external cut utility by doing something like this:

stat=($(</proc/$$/stat))    # create an array
ppid=${stat[3]}             # get the fourth field

If the command might have space(s) in its name, you can count from the end of the array (assuming that the number of fields is stable). This will also work if there are no spaces in the command's name.

ppid=${stat[-49]}           # gets the same field but counts from the end

Here's another technique which should avoid those problems (but may fail if the command name contains a newline):

mapfile -t stat < /proc/$$/status
ppid=${stat[5]##*$'\t'}

The fifth field in that file looks like:

PPid:    1234

and the brace expansion strips the everything up to the tab character leaving just the numeric part.


Another solution (from here):

ps -p $$ -o ppid=

Failing a better solution, here's a simple (recursive) script to get the top-level parent PID of any process number you give it (or the current shell if you leave out the PID argument):

#!/bin/bash
# Look up the top-level parent Process ID (PID) of the given PID, or the current
# process if unspecified.

function top_level_parent_pid {
    # Look up the parent of the given PID.
    pid=${1:-$$}
    stat=($(</proc/${pid}/stat))
    ppid=${stat[3]}

    # /sbin/init always has a PID of 1, so if you reach that, the current PID is
    # the top-level parent. Otherwise, keep looking.
    if [[ ${ppid} -eq 1 ]] ; then
        echo ${pid}
    else
        top_level_parent_pid ${ppid}
    fi
}

Just source this script and call top_level_parent_pid with or without a PID argument, as appropriate.

Thanks to @Dennis Williamson for his many suggestions on how to write this script compactly and efficiently.


Iterative version:

# ppid -- Show parent PID
# $1 - The process whose parent you want to show, default to $$
function ppid() {
    local stat=($(</proc/${1:-$$}/stat))
    echo ${stat[3]}
}

# apid -- Show all ancestor PID
# $1 - The process whose ancestors you want to show, default to $$
# $2 - Stop when you reach this ancestor PID, default to 1
function apid() {
    local ppid=$(ppid ${1:$$})
    while [ 0 -lt $ppid -a ${2:-1} -ne $ppid ]; do
        echo $ppid
        ppid=$(ppid $ppid)
    done
}

As two separate functions, because sometimes you want parent PID only, and sometimes you want the whole tree.

Tags:

Bash

Process