我似乎误解了创建子 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。
换句话说,基本情况是:
( … )
创建一个子shell。原始进程调用fork
和wait
。在子进程中,这是一个子shell:sleep
是一个外部命令,需要子进程的子进程。子 shell 调用fork
和wait
。在子子流程中:- 子子进程执行外部命令 →
exec
。 - 最终命令终止 →
exit
。
- 子子进程执行外部命令 →
wait
在子 shell 中完成。
wait
在原来的流程中完成。
优化是:
( … )
创建一个子shell。原始进程调用fork
和wait
。在子进程中,它是一个子 shell,直到它调用exec
:sleep
是一个外部命令,这是该进程需要做的最后一件事。- 子进程执行外部命令→
exec
。 - 最终命令终止 →
exit
。
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。