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

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

我似乎误解了创建子 shell 的 Bash 规则。我认为括号总是创建一个子 shell,它作为自己的进程运行。

然而,情况似乎并非如此。在代码片段 A(如下)中,第二个sleep命令不在单独的 shell 中运行(由pstree另一个终端确定)。然而,在代码片段 B 中,第二个sleep命令在单独的 shell 中运行。这些片段之间的唯一区别是第二个片段在括号内有两个命令。

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

代码片段A:

sleep 5
(
sleep 5
)

代码片段B:

sleep 5
(
x=1
sleep 5
)

答案1

括号始终启动一个子 shell。发生的情况是 bash 检测到这sleep 5是该子 shell 执行的最后一个命令,因此它调用exec而不是fork+exec。该sleep命令替换同一进程中的子 shell。

换句话说,基本情况是:

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

优化是:

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

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

当您在调用之前添加其他内容时sleep,可以进行优化(ksh 会这样做),但 bash 不会这样做(这种优化非常保守)。

答案2

来自高级 Bash 编程指南:

“一般来说,脚本中的外部命令会分叉子进程,而 Bash 内置命令不会。因此,与外部命令等价物相比,内置命令执行速度更快,使用的系统资源更少。”

再往下一点:

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

例子:

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

使用 OP 代码的示例(睡眠时间较短,因为我不耐烦):

echo $BASHPID

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

输出:

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

答案3

对 @Gilles 答案的补充说明。

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

但是,此类子 shell 具有的数字可能会重复:

$ (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 的子 shell。

即:如果shell没有重新初始化,$$是一样的。

或者用这个:

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

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

$$当前 shell(不是子 shell)的 ID。

相关内容