如何使用 bash 找到给定进程的顶级父 PID?

发布于 2024-09-16 00:16:46 字数 1346 浏览 7 评论 0原文

假设我运行 ps axf 并且可以看到我的命令的进程树如下所示:

  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

我知道我可以检查 $ 当前 shell 的 PID (10785) 或 < code>$PPID 为父 PID (10778)。

但我只想要顶级父 PID,在本例中为 800(SSH 守护进程)。有什么办法可以轻松做到这一点吗?

我从这个SO答案得知我可以递归地检查 /proc/PID/stat 文件中的第四个条目来查找每个进程的父 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

顶级父 PID 将是我到达 init 的 PID,即 1。)

在我编写一个小循环(我什至不确定是否可以在 bash 中使用递归)来执行此操作之前,是否有一个我缺少的更简单的方法?也许只是 /proc 下文件的另一个参数?对这些文件的 grep 没有显示任何明显的信息。

编辑:当然,所有Linux进程的顶级进程是/sbin/init,PID为1。我想要的是之前的父进程的PID:倒数第二个父进程。

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

一梦浮鱼 2024-09-23 00:16:46

如果没有更好的解决方案,这里有一个简单的(递归)脚本来获取您给它的任何进程号的顶级父 PID(如果您省略 PID 参数,则为当前 shell):

#!/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
}

只需 source此脚本并根据需要调用带或不带 PID 参数的 top_level_parent_pid

感谢@Dennis Williamson 对于如何紧凑而高效地编写此脚本提出了许多建议。

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.

┼── 2024-09-23 00:16:46

Bash 绝对可以做递归。

您可以通过执行以下操作从 stat 文件中检索第四个字段,而无需使用外部 cut 实用程序:

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

如果该命令的名称中可能包含空格,您可以从该命令的末尾开始计数数组(假设字段数量稳定)。如果命令名称中没有空格,这也将起作用。

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

这是另一种应该避免这些问题的技术(但如果命令名称包含换行符,则可能会失败):

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

该文件中的第五个字段如下所示:

PPid:    1234

并且大括号扩展将所有内容都剥离到制表符,只留下数字部分。

\t'}

该文件中的第五个字段如下所示:

并且大括号扩展将所有内容都剥离到制表符,只留下数字部分。

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]##*

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.

\t'}

The fifth field in that file looks like:

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

明天过后 2024-09-23 00:16:46

另一个解决方案(来自此处):

ps -p $ -o ppid=

Another solution (from here):

ps -p $ -o ppid=
君勿笑 2024-09-23 00:16:46

迭代版本:

# 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
}

作为两个独立的函数,因为有时您只需要父 PID,有时您需要整个树。

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.

琉璃梦幻 2024-09-23 00:16:46

我改进了您 (@yukondude) 的递归解决方案,以避免命令名称包含内部字段分隔符 (IFS) 字符(例如空格、制表符和换行符)的问题,这些字符是合法的 Unix 文件名字符。

#!/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
}

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
}
爱给你人给你 2024-09-23 00:16:46

OS X 版本,改编自 @albert 和 @yukondude 的回答:

#!/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
}

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
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文