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


375

我从shell脚本启动了一个后台进程,我想在脚本结束后终止该进程。

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


8
$!是正确的。您确定要在BG中启动脚本吗?请来样。
pixelbeat

7
是的!是正确的。我错了。
Volodymyr Bezuglyy,2009年

11
$$包含当前脚本PID。
2013年

Answers:


582

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

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

您不能使用作业控制,因为它是一种交互式功能,并且与控制终端绑定在一起。脚本完全不必附加终端,因此作业控制不一定可用。


22
由于$!返回最后一个后台进程的pid。是否有可能在foo和之间开始某个东西$!,而我们得到的是pid而不是foo
WiSaGaN

53
@WiSaGaN:否。这两行之间什么都没有。系统上的任何其他活动都不会影响此。$!将扩展为该Shell中最后一个后台进程的PID 。
camh 2013年

8
...如果foo碰巧是多个管道命令(例如tail -f somefile.txt | grep sometext),它将为您提供帮助。在这种情况下,$!如果您要查找的是grep命令的PID,而不是tail命令。在这种情况下,您将需要使用jobsps或类似符号。
约翰·里克斯

1
@JohnRix:不一定。您将获得的pid grep,但是如果您将其杀死,则在尝试向管道写入数据时,tail将获得SIGPIPE。但是,一旦您尝试进行任何棘手的过程管理/控制,bash / shell就会变得很痛苦。
卡姆(Camh)2014年

5
另一个有价值的解决方案在“ 如何获得刚刚启动的过程的pid”中提出了一个建议:哦,还有“ oneliner”:/bin/sh -c 'echo $$>/tmp/my.pid && exec program args' &– sysfault 2010年11月24日,14:28
imz – Ivan Zakharyaschev

148

您可以使用jobs -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

6
@Phil仅列出pid:jobs -p。要列出某个作业的pid:作业-p%3。无需处理输出。
Erik Aronesty,2014年

1
@Erik使用不同的命令/参数可以更改我的注释的上下文。如果没有建议其他参数,则需要处理输出。建议改善答案!
Phil

$!在启动后立即保存PID,在大多数情况下更可移植且更直接。这就是当前接受的答案。
三胞胎

jobs -p返回与jobs -lLubuntu 16.4
Timo

46
  • $$ 是当前脚本的pid
  • $! 是最后一个后台进程的pid

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

$ echo $$
3748

$ sleep 100 &
[1] 192

$ echo $!
192

$ kill %1

[1]+  Terminated              sleep 100

回声%1并不在我的Ubuntu返回后台进程而echo $!
蒂莫

25

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

pkill -P $$

-P标志与pkill和的工作方式相同pgrep-它获取子进程,仅pkill在杀死子进程且将pgrep子PID打印到stdout 时才起作用。


很方便!这是确保您不会在后台保留打开的进程的最佳方法。
lepe 2015年

@lepe:不完全是。如果您是祖父母,这将不起作用:bash -c 'bash -c "sleep 300 &"' & 跑步后pgrep -P $$什么也不显示,因为睡眠不会成为您外壳的直接子对象。
petre '16

1
@AlexeyPolonsky:应该是:杀死shell的所有子进程,而不是脚本。因为$$是指当前的外壳。
Timo

表演bash -c 'bash -c "sleep 300 &"' & ; pgrep -P $$,我上stdout [1] <pid>. So at least it shows something, but this is probably not the output of pgrep`
Timo

甚至进程也可以使它们脱离父进程。一个简单的技巧是调用fork(创建一个孙代),然后在孙代继续执行工作时让子进程退出。(守护进程是关键字)但是,即使子进程继续运行,pkill -P也不足以将信号传播给子进程。需要像pstree这样的工具来遵循整个依赖进程树。但这不会捕获从进程启动的守护进程,因为它们的父进程是进程1。例如:bash -c 'bash -c "sleep 10 & wait $!"' & sleep 0.1; pstree -p $$
Pauli Nieminen

4

这就是我所做的。检查一下,希望对您有所帮助。

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

3

您也许还可以使用pstree:

pstree -p user

通常,它以文本形式表示“用户”的所有进程,而-p选项给出进程ID。据我了解,它并不依赖于当前Shell拥有进程。它还显示了叉子。


1
要在shell脚本中使用它,您必须大量处理输出。
Phil

1

pgrep可以获取父进程的所有子PID。如前所述$$,当前脚本是PID。因此,如果您希望脚本在执行后自行清理,则可以做到这一点:

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

这不会杀死很多吗?
Phil

是的,这个问题从来没有提到退出时让一些有背景的孩子活着。
errant.info 2013年

trap 'pkill -P $$' SIGING SIGTERM EXIT 看起来更简单,但我没有对其进行测试。
petre '16

为了兼容性,请勿使用SIG前缀。POSIX允许使用,但作为实现的扩展可能支持:pubs.opengroup.org/onlinepubs/007904975/utilities/trap.html例如,破折号外壳不支持。
josch
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.