在Bash中调用subshel​​l的规则?


24

我似乎误解了Bash创建子shell的规则。我认为括号总会创建一个子外壳,该子外壳将作为其自身的进程运行。

但是,事实并非如此。在代码片段A(如下)中,第二条sleep命令未在单独的外壳程序中运行(由pstree另一个终端确定)。但是,在代码片段B中,第二个sleep命令确实在单独的外壳运行。这些代码段之间的唯一区别是第二个代码段在括号内有两个命令。

有人可以解释一下创建子外壳的规则吗?

代码片段A:

sleep 5
(
sleep 5
)

代码片段B:

sleep 5
(
x=1
sleep 5
)

Answers:


20

括号总是开始一个子shell。发生的是bash检测到sleep 5该子shell执行的最后一条命令,因此它调用exec而不是fork+ exec。该sleep命令将在同一过程中替换子外壳。

换句话说,基本情况是:

  1. ( … )创建一个子shell。原始进程调用forkwait。在子过程中,它是一个子shell:
    1. sleep是需要子过程的子过程的外部命令。子外壳调用forkwait。在子子流程中:
      1. 子子过程执行外部命令→ exec
      2. 最终,命令终止→ exit
    2. wait 在子外壳中完成。
  2. wait 在原始过程中完成。

优化是:

  1. ( … )创建一个子shell。原始进程调用forkwait。在子进程中,它是一个子外壳,直到调用exec
    1. sleep 是一个外部命令,这是此过程需要做的最后一件事。
    2. 子进程执行外部命令→ exec
    3. 最终,命令终止→ exit
  2. wait 在原始过程中完成。

当您在调用之后添加其他内容时sleep,需要保留子外壳,因此不会发生这种优化。

当您在调用之前添加其他内容时sleep,可以进行优化(而ksh可以进行优化),但是bash不能进行优化(此优化非常保守)。


通过调用创建Subshel​​l,并通过调用创建fork子进程(以执行外部命令)fork + exec。但您的第一段建议也fork + exec将其称为subshel​​l。我在这里错了吗?
鹰头

1
子外壳程序不调用@haccks fork+ exec,而外部命令则调用它。如果不进行任何优化,fork则会调用该子shell,并调用另一个用于外部命令。我在回答中添加了详细的流程描述。
吉尔斯(Gilles)'所以

非常感谢您的更新。现在,它解释得更好。我可以从中推断出,在(...)(基本情况下)情况下,exec是否调用取决于子外壳程序是否具有要执行的外部命令,而在执行任何外部命令的情况下必须存在fork + exec
haccks

还有一个问题:这种优化仅适用于子Shell,还是可以针对诸如dateShell中的命令进行?
鹰头

@哈克斯我不明白这个问题。这种优化是关于调用外部命令作为Shell进程的最后一件事。它不仅仅限于子弹:比较strace -f -e clone,execve,write bash -c 'date'strace -f -e clone,execve,write bash -c 'date; true'
吉尔斯(Gills)'所以

4

从《高级Bash编程指南》中

“通常,脚本中的外部命令会派生一个子进程,而Bash内置的命令则不会。因此,与它们的外部命令相比,内置的命令执行更快,使用的系统资源更少。”

还有一点点向下:

“嵌入在括号之间的命令列表作为子外壳运行。”

例子:

[root@talara test]# echo $BASHPID
10792
[root@talara test]# (echo $BASHPID)
4087
[root@talara test]# (echo $BASHPID)
4088
[root@talara test]# (echo $BASHPID)
4089

使用OPs代码的示例(由于我不耐烦,所以睡眠时间较短):

echo $BASHPID

sleep 2
(
    echo $BASHPID
    sleep 2
    echo $BASHPID
)

输出:

[root@talara test]# bash sub_bash
6606
6608
6608

2
感谢蒂姆的答复。我不确定它是否能完全回答我的问题。由于“嵌入在括号之间的命令列表作为子外壳程序运行”,我希望第二个命令列表在子外壳程序sleep中运行(由于它是内置的而不是子外壳程序的子进程,因此可能在子外壳程序的进程中运行)。但是,无论如何,我都希望存在一个子外壳,即父Bash进程下的Bash子进程。对于上面的代码段B,似乎并非如此。
害羞的2012年

更正:因为sleep它似乎不是内置的,所以我希望sleep两个代码片段中的第二个调用都可以在subshel​​l进程的子进程中运行。
害羞的2012年

@bashful我采取了用我的$BASHPID变量来破坏代码的自由。可悲的是,您这样做的方式并没有给我带来整个故事。请参阅答案中我添加的输出。
蒂姆(Tim)

4

@Gilles答案的附加说明。

正如吉勒斯所说: The parentheses always start a subshell.

但是,此类子外壳程序具有的数字可能会重复:

$ (echo "$BASHPID and $$"; sleep 1)
2033 and 31679
$ (echo "$BASHPID and $$"; sleep 1)
2040 and 31679
$ (echo "$BASHPID and $$"; sleep 1)
2047 and 31679

如您所见,$$不断重复,这与预期的一样,因为(执行此命令以找到正确的man bash行):

$ LESS=+/'^ *BASHPID' man bash

BASHPID
扩展为当前bash进程的进程ID。在某些情况下,这与$$不同,例如不需要重新初始化bash的子外壳。

也就是说:如果未重新初始化外壳,则$$是相同的。

或与此:

$ LESS=+/'^ *Special Parameters' man bash

特殊参数
$扩展到外壳的进程ID。在()子外壳程序中,它扩展为当前外壳程序的进程ID,而不是子外壳程序。

$$是当前的壳(未子shell)的ID。


1
在特定部分打开bash联机帮助页的不错技巧
Daniel Serodio
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.