如何获取后台进程的进程ID?

Posted

技术标签:

【中文标题】如何获取后台进程的进程ID?【英文标题】:How to get process ID of background process? 【发布时间】:2010-12-26 21:18:39 【问题描述】:

我从我的 shell 脚本启动一个后台进程,我想在我的脚本完成后终止这个进程。

如何从我的 shell 脚本中获取此进程的 PID?据我所知,变量$! 包含当前脚本的PID,而不是后台进程。

【问题讨论】:

$!是正确的。您确定要在 BG 中启动脚本吗?请提供样品。 是的 $!是正确的。我错了。 $$ 包含当前脚本 PID。 注意$$可能是bash中的父PID:testfun() echo "\$\$=$$ \$BASHPID=$BASHPID"; ; echo "my pid is $$"; testfun & wait 【参考方案1】:

您可以使用jobs -l 命令获取特定的作业L

^Z
[1]+  Stopped                 guard

my_mac:workspace r$ jobs -l
[1]+ 46841 Suspended: 18           guard

在这种情况下,46841 是 PID。

来自help jobs

-l 报告作业的进程组ID和工作目录。

jobs -p 是另一个仅显示 PID 的选项。

【讨论】:

要在 shell 脚本中使用它,您必须处理输出。 @Phil 仅列出 pid:jobs -p。列出某个工作的 pid:jobs -p %3。无需处理输出。 @Erik 使用不同的命令/参数可以更改我评论的上下文。如果没有建议的附加参数,则输出需要处理。建议改进答案! 在您开始之后从$! 保存 PID,在大多数情况下,它更便携、更直接。这就是当前接受的答案的作用。【参考方案2】: $$ 是当前脚本的 pid $!是最后一个后台进程的pid

这是 bash 会话的示例记录(%1 是指从 jobs 看到的后台进程的序号):

$ echo $$
3748

$ sleep 100 &
[1] 192

$ echo $!
192

$ kill %1

[1]+  Terminated              sleep 100

【讨论】:

echo %1 不会返回我的 Ubuntu 上的后台进程,而 echo $! 请注意,$$ 并不总是当前的 PID。例如,如果您在bash 中定义一个新函数并在后台运行该函数,则该函数中的$$ 包含在后台启动该函数的进程的PID。如果您需要运行任何给定代码的实际进程的 PID,则必须改用$BASHPID【参考方案3】:

需要在启动时保存后台进程的PID:

foo &
FOO_PID=$!
# do other stuff
kill $FOO_PID

您不能使用作业控制,因为这是一项交互功能并且与控制终端相关联。脚本不一定要连接终端,因此作业控制不一定可用。

【讨论】:

自 $!返回最后一个后台进程的 pid。是否有可能在foo$! 之间开始某些东西,而我们得到的是某个东西的pid 而不是foo 的? @WiSaGaN:不。这些行之间没有任何内容。系统上的任何其他活动都不会影响这一点。美元!将扩展为最后一个后台进程的PID在那个shell中 ... 如果foo 恰好是多个管道命令(例如tail -f somefile.txt | grep sometext),它会为您服务。在这种情况下,您将从$! 获取grep 命令的PID,而不是tail 命令(如果您正在寻找的话)。在这种情况下,您将需要使用 jobsps 或类似名称。 @JohnRix:不一定。你会得到grep 的pid,但是如果你杀了它,tail 在尝试写入管道时会得到一个SIGPIPE。但是一旦你尝试进入任何棘手的进程管理/控制,bash/shell 就会变得相当痛苦。 另一个有价值的解决方案在How to get pid of just started process 中提出(对答案的评论):哦,“oneliner”:/bin/sh -c 'echo $$>/tmp/my.pid && exec program args' & – sysfault 2010 年 11 月 24 日 14:28【参考方案4】:

你也可以使用 pstree:

pstree -p user

这通常为“用户”提供所有进程的文本表示,-p 选项提供进程 ID。据我了解,它不依赖于让进程归当前外壳所有。它还显示了叉子。

【讨论】:

要在 shell 脚本中使用它,您必须大量处理输出。【参考方案5】:

这就是我所做的。看看吧,希望对你有帮助。

#!/bin/bash
#
# So something to show.
echo "UNO" >  UNO.txt
echo "DOS" >  DOS.txt
#
# Initialize Pid List
dPidLst=""
#
# Generate background processes
tail -f UNO.txt&
dPidLst="$dPidLst $!"
tail -f DOS.txt&
dPidLst="$dPidLst $!"
#
# Report process IDs
echo PID=$$
echo dPidLst=$dPidLst
#
# Show process on current shell
ps -f
#
# Start killing background processes from list
for dPid in $dPidLst
do
        echo killing $dPid. Process is still there.
        ps | grep $dPid
        kill $dPid
        ps | grep $dPid
        echo Just ran "'"ps"'" command, $dPid must not show again.
done

然后运行它为:./bgkill.sh 当然具有适当的权限

root@umsstd22 [P]:~# ./bgkill.sh
PID=23757
dPidLst= 23758 23759
UNO
DOS
UID        PID  PPID  C STIME TTY          TIME CMD
root      3937  3935  0 11:07 pts/5    00:00:00 -bash
root     23757  3937  0 11:55 pts/5    00:00:00 /bin/bash ./bgkill.sh
root     23758 23757  0 11:55 pts/5    00:00:00 tail -f UNO.txt
root     23759 23757  0 11:55 pts/5    00:00:00 tail -f DOS.txt
root     23760 23757  0 11:55 pts/5    00:00:00 ps -f
killing 23758. Process is still there.
23758 pts/5    00:00:00 tail
./bgkill.sh: line 24: 23758 Terminated              tail -f UNO.txt
Just ran 'ps' command, 23758 must not show again.
killing 23759. Process is still there.
23759 pts/5    00:00:00 tail
./bgkill.sh: line 24: 23759 Terminated              tail -f DOS.txt
Just ran 'ps' command, 23759 must not show again.
root@umsstd22 [P]:~# ps -f
UID        PID  PPID  C STIME TTY          TIME CMD
root      3937  3935  0 11:07 pts/5    00:00:00 -bash
root     24200  3937  0 11:56 pts/5    00:00:00 ps -f

【讨论】:

【参考方案6】:

pgrep 可以获得父进程的所有子 PID。如前所述,$$ 是当前脚本的 PID。因此,如果您想要一个自行清理的脚本,这应该可以解决问题:

trap 'kill $( pgrep -P $$ | tr "\n" " " )' SIGINT SIGTERM EXIT

【讨论】:

这不会杀死很多人吗? 是的,这个问题从来没有提到在退出时保持一些背景孩子活着。 trap 'pkill -P $$' SIGING SIGTERM EXIT 看起来比较简单,但我没有测试。 为了兼容性,不要使用SIG前缀。它是 POSIX 允许的,但只是作为实现可能支持的扩展:pubs.opengroup.org/onlinepubs/007904975/utilities/trap.html 例如,dash shell 不支持。 假设如果你有pgrep,你可能也有pkill,较短的版本更简洁。【参考方案7】:

杀死 bash 脚本的所有子进程的更简单方法:

pkill -P $$

-P 标志与 pkillpgrep 的工作方式相同 - 它获取子进程,只有 pkill 子进程被杀死,pgrep 子 PID 被打印到标准输出。

【讨论】:

很方便!这是确保您不会将打开的进程留在后台的最佳方法。 @lepe:不完全是。如果您是祖父母,这将不起作用:在 bash -c 'bash -c "sleep 300 &"' & 运行 pgrep -P $$ 之后什么都没有显示,因为 sleep 不会是您 shell 的直接子代。 @AlexeyPolonsky:应该是:杀死 shell 的所有子进程,而不是脚本。因为$$ 指的是当前的shell。 执行bash -c 'bash -c "sleep 300 &"' & ; pgrep -P $$,我进入标准输出[1] <pid>. So at least it shows something, but this is probably not the output of pgrep` 甚至进程也可以将它们与父进程分离。简单的技巧是调用 fork (创建一个孙子),然后让子进程退出,而孙子继续工作。 (守护进程是关键字)但是即使孩子一直在运行, pkill -P 也不足以将信号传播给孙子。需要像 pstree 这样的工具来跟踪整个依赖进程树。但这不会捕获从进程启动的守护进程,因为它们的父进程是进程 1。例如:bash -c 'bash -c "sleep 10 & wait $!"' & sleep 0.1; pstree -p $$【参考方案8】:

我在配置各种基础设施对象时多次遇到这个问题。很多时候,您需要使用 kubectl 或临时端口转发的临时代理。我发现timeout 命令是解决这些问题的好方法,因为它允许我的脚本是自包含的,并且我可以确信该过程将结束。如果我仍然需要它,我会尝试设置小的超时并重新运行脚本。

【讨论】:

以上是关于如何获取后台进程的进程ID?的主要内容,如果未能解决你的问题,请参考以下文章

linux获取后台进程的控制台数据

如何检查正在运行的进程是不是是后台进程? [关闭]

Java如何得到当前进程ID号

我如何知道何时可以安全地重用来自另一个进程的后台 NSURLSessionConfiguration id?

如何获取上一个关注的应用程序的进程 ID

HapiJS 启动更长的后台进程