tags:

views:

57

answers:

2

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.

+2  A: 

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
Dennis Williamson
Thanks for that suggestion. I used it in the little script I posted as a possible solution.
yukondude
+1  A: 

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
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
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
+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