如何获取后台进程的进程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 命令(如果您正在寻找的话)。在这种情况下,您将需要使用 jobs
或 ps
或类似名称。
@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
标志与 pkill
和 pgrep
的工作方式相同 - 它获取子进程,只有 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?的主要内容,如果未能解决你的问题,请参考以下文章