21

Let's say I run ps axf and I can see that my command's process tree looks like this:

  800 ?        Ss     0:00 /usr/sbin/sshd
10186 ?        Ss     0:00  \_ sshd: yukondude [priv]
10251 ?        S      0:00      \_ sshd: yukondude@pts/0
10252 pts/0    Ss     0:00          \_ -bash
10778 pts/0    S      0:00              \_ su -
10785 pts/0    S      0:00                  \_ -su
11945 pts/0    R+     0:00                      \_ ps axf

I know I can check $$ for the current shell's PID (10785) or $PPID for the parent PID (10778).

But I just want the top-level parent PID, which would be 800 (SSH daemon) in this example. Is there any way to do that easily?

I learned from this SO answer that I can recursively check the 4th entry in the /proc/PID/stat file to find each process's parent PID:

# cut -f4 -d' ' /proc/10785/stat
10778
# cut -f4 -d' ' /proc/10778/stat
10252
# cut -f4 -d' ' /proc/10252/stat
10251
# cut -f4 -d' ' /proc/10251/stat
10186
# cut -f4 -d' ' /proc/10186/stat
800
# cut -f4 -d' ' /proc/800/stat
1

(The top-level parent PID will be the one just before I reach init's PID, i.e., 1.)

Before I write a little loop (I'm not even sure if you can use recursion in bash) to do this, is there a much more straightforward method that I'm missing? Maybe just another parameter of a file under /proc? A grep through those files didn't reveal anything obvious.

Edit: Of course, the top-level process for all Linux processes is /sbin/init with a PID of 1. What I want is the PID of the parent just before that: the penultimate parent.

Community
  • 1
  • 1
yukondude
  • 24,013
  • 13
  • 49
  • 58
  • 1
    You don't need recursion for this, just a simple loop... – JanC Aug 27 '10 at 18:12
  • 1
    True, but recursion is just so much more fun and computer-sciency. – yukondude Aug 27 '10 at 18:32
  • 1
    No way. Loops are more fundamental :P Dont' forget your most basic tools. – Matt Joiner Jul 06 '11 at 03:19
  • science includes finding more efficient ways to achieve the same result. Not more complex, and expensive ways... – Felipe Alvarez Jul 03 '13 at 07:56
  • @MattJoiner Yes and no. Most often, loops and call stacks are the means by which recursion is implemented. But that's not necessarily the case- especially in languages which implement Tail Call Optimization- and since the most intuitive and obvious solution is often recursive, dismissing recursion offhand is unwise. Recursion and iteration are both powerful tools- in abstract math, computer science, and software engineering. However, the bash implementation of recursion involves forking, so I'd avoid it if I were you. – Parthian Shot Jul 08 '15 at 17:58

6 Answers6

13

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.

Dennis Williamson
  • 346,391
  • 90
  • 374
  • 439
  • Thanks for that suggestion. I used it in the little script I posted as a possible solution. – yukondude Aug 27 '10 at 22:14
  • 1
    Old thread I know, but I thought I'd throw in a 'sh' friendly version of the above.. read _ _ _ ppid _ < /proc/${pid}/stat – Dave Mar 06 '15 at 04:51
  • ppid won't be correct if the command name had at least one whitespace character. – Wis May 03 '19 at 23:02
  • @Wis: added an alternative which avoids that problem (at the potential expense of a different one). – Dennis Williamson May 03 '19 at 23:25
  • @DennisWilliamson I couldn't find where in the manpage it says that the number of fields is a constant. my idea was to iterate from the beginning until you find a field that ends with a `)` and return the field that comes after the field after it, which seemed to usually be `S`. – Wis May 04 '19 at 00:22
  • @Wis: I added another technique. – Dennis Williamson May 04 '19 at 11:36
13

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.

yukondude
  • 24,013
  • 13
  • 49
  • 58
  • 1
    I would suggest using a function within the script to do the recursion rather than the whole script. It just seems neater. Also, the way it is, your script would have to be in your `$PATH` or you'd have to use `$0 $ppid` as the second to last line (the recursive call). By the way, your first `if...fi` can be replaced by just this: `pid=${1:-$$}`. – Dennis Williamson Aug 28 '10 at 01:34
  • Excellent tips. I always have to look up bash parameter substitution rules when I encounter them, but they are space savers. I also considered a function but I got lazy after this little script did the job. I'll have to spruce it up if it ends up being a more frequently used utility. – yukondude Aug 28 '10 at 03:13
  • 1
    +1 Looks good. You could also put the default inside the function. Then if you source the file so the function is "resident" it can be called without any argument. `pid=${1:-$$}` – Dennis Williamson Aug 28 '10 at 17:12
  • This is a really nice script. Nice work! Also you should not have used community wiki, the answer still deserves the rep. I ended up writing something just like this in Python, it's nice to see someone else did it the same way. – Matt Joiner Jul 06 '11 at 03:21
5

Another solution (from here):

ps -p $$ -o ppid=
Community
  • 1
  • 1
Albert
  • 65,406
  • 61
  • 242
  • 386
1

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.

bishop
  • 37,830
  • 11
  • 104
  • 139
1

I improved upon your (@yukondude)'s recursive solution to avoid issues where the command name contains internal field separator (IFS) characters like space, tab, and newline, which are legal Unix filename characters.

#!/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:-$$}
    ppid="$(awk '/^PPid:/ { print $2 }' < /proc/"$pid"/status)"
    # /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
}
Wis
  • 484
  • 7
  • 22
0

OS X version, adapted from @albert and @yukondude's answers:

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

# From http://stackoverflow.com/questions/3586888/how-do-i-find-the-top-level-parent-pid-of-a-given-process-using-bash
function top_level_parent_pid {
    # Look up the parent of the given PID.
    PID=${1:-$$}
    PARENT=$(ps -p $PID -o ppid=)

    # /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 [[ ${PARENT} -eq 1 ]] ; then
        echo ${PID}
    else
        top_level_parent_pid ${PARENT}
    fi
}
mikemaccana
  • 110,530
  • 99
  • 389
  • 494