如何在命令行中获取Bash子进程的进程ID

33

我知道在bash中我们可以使用圆括号()创建子shell。根据bash的说明文档:

(list) list  is  executed  in  a  subshell environment 

获取当前进程 ID的方法如下:

echo $$

现在我的问题是如何获取使用命令行中的 () 创建的子shell的进程ID?

如果我使用这个:

echo $$; ( echo $$; ) 

由于$$在子shell创建之前就已被扩展,因此我将在stdout上打印父Shell的进程ID两次。那么如何真正强制延迟扩展?

[解决方案应该适用于Mac,而不仅仅是Linux]

更新:

建议的链接答案不起作用,因为echo $BASHPID在我的Mac上无法工作并返回空白。


4
我认为BASHPID需要Bash 4版本,而OS X默认安装的是Bash 3.2。 - Lily Ballard
1
@anubhava:没错,但是我们(SO社区)直到你编辑之前都不知道这一点;-) - David Z
1
是的,但是在另一个问题中没有任何迹象表明那里提供的解决方案在 Mac 上不起作用。这是使它不是重复的关键点。 - David Z
1
有趣的是,$$在子shell中不起作用的原因并不完全是你在问题中所指示的。如果问题只是$$在子shell代码执行之前被扩展,那么你可以通过(eval echo '$$')轻松解决这个问题,但实际上,这也不起作用。根据http://tldp.org/LDP/abs/html/internalvariables.html上的最后一个脚注,$$仅返回顶层的pid,没有其他说明。虽然它没有说明原因。我不知道这个问题的答案。 - frankc
1
请注意,如果您只想获取子进程PID(而不是括号内的PID),那么这很容易并且可用:http://serverfault.com/questions/205498/how-to-get-pid-of-just-started-process - rogerdpack
显示剩余4条评论
6个回答

19

感谢大家在这里花费宝贵的时间来寻找答案。

然而,我现在自己回答我的问题了,因为我找到了一种“hack”方法,在bash ver < 4上获取此pid(尽管将适用于所有版本)。以下是命令:

echo $$; ( F='/tmp/myps'; [ ! -f $F ] && echo 'echo $PPID' > $F; )

它打印出:

5642
13715

13715是子shell的pid。为了测试这个,当我执行以下命令时:

echo $$; ( F='/tmp/myps'; [ ! -f $F ] && echo 'echo $PPID' > $F; bash $F; ps; )

我得到了这个:

5642
13773
  PID   TT  STAT      TIME COMMAND
 5642 s001  S      0:02.07 -bash
13773 s001  S+     0:00.00 -bash
告诉我13773确实是子shell的进程ID。 注意:我回到了原来的解决方案,因为像@ChrisDodd评论的那样,在Linux上echo $$; (bash -c 'echo $PPID';)不起作用。我的上述解决方案在Mac和Linux上都适用。

3
"sh -c 'echo $PPID'" 是一个更简洁的执行相同操作的方式。 - evil otto
3
有个奇怪的现象:当我在我的电脑(Linux)上运行 echo $$; ( bash -c 'echo $PPID'; ) 时,它会打印两遍相同的 pid。 - Chris Dodd
@ChrisDodd:非常有价值的观察,我已经恢复到我的原始解决方案。 - anubhava
6
@ChrisDodd 命令组没有在子shell中运行,因为其命令列表只包含一个命令(这是一种未记录的优化方式)。使用echo $$; (:; bash -c 'echo $PPID')来强制使用子shell。 - ecatmur
1
@ChrisDodd 如果你在shell中尝试过,那么它应该按预期工作。尝试这个命令以查看不同的值:(echo $$; (sh -c 'echo $PPID';)) 在Ubuntu 12.04和OSX 10.9.2下,我也可以正常使用。 - Maxim Kholyavkin
1
就我个人而言,这个方法对我有效:echo $$; (echo 'echo $PPID' | bash) 这只是上面方法的另一种变化。 - Michael C. Chen

8

很不幸,在bash版本4之前并没有简单的方法来做到这一点,因为这时候$BASHPID才被引入。你可以做的一件事情是编写一个小程序,打印其父进程的PID:

int main()
{
    printf("%d\n", getppid());
    return 0;
}

如果您将其编译为ppid并将其放在路径中,您可以调用它,例如:

$ (echo $$; ppid)
2139
29519
$ (x=$(ppid); echo $x)
29521

然而,我注意到一个奇怪的问题,如果你写下

$ (ppid)

似乎并没有真正在子shell中运行它 -- 你需要至少在括号内放置两个命令,以便bash实际上在子shell中运行它们。


5

您可以做:

$ ( your_action ) &
[1] 44012

并且可以像这样查找子进程的PID:

$ echo "The sub PID : $!"
The Sub PID : 44012

$! 返回后台进程的最后一个PID。(详见这个手册


不错的尝试,但它只在下一个命令和子 shell 外部获取子进程的 ID。 - anubhava

2
使用Homebrew在Mac上安装pgrep:brew install pgrep 请查看链接以安装Homebrew:Link

抱歉在原本无关的问题下发布回答,但我无法在之前的帖子上发表评论。 - Gerald Boersma

1

这似乎可以工作:

(echo $$; echo  `ps axo pid,command,args | grep "$$" |awk '{ getline;print $1}'`)
14609
17365

由于某种原因,OSX受到限制并且不带有pgrep,或者可以使用以下方法(在Linux中有效):

 (echo $$; echo  `pgrep -P $$`) 
 14609
 17390

1
不是这样的,由于括号内有3个管道符,这将打印子+子+子+子shell的pid。 - anubhava
Anubhava,您能否看一下这个问题。对你来说应该很容易。这是链接:http://stackoverflow.com/questions/31961615/what-to-change-in-htaccess-so-it-stop-showing-subdomain - user3082821

1
您可以通过回显父进程的BASHPID来使用父进程的ppid,然后将该进程放在后台,并使用父进程pid查找ppid中的pid。例如,要获取子shell中后台运行的sleep 555命令的pid
(echo "$BASHPID" > /tmp/_tmp_pid_ && sleep 555 &) && ps -ho pid --ppid=$(< /tmp/_tmp_pid_)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接