Reputation: 24643
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.
Upvotes: 21
Views: 30111
Reputation: 514
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
}
Upvotes: 1
Reputation: 359855
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.
Upvotes: 13
Reputation: 39354
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.
Upvotes: 1
Reputation: 123058
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
}
Upvotes: 0
Reputation: 24643
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.
Upvotes: 13