在我的书中(索贝尔的Linux 实用指南, 4e) 文中写道
您可以使用括号控制运算符对命令进行分组。当您使用此技术时,shell 会为每个组创建一个自身的副本,称为子 shell。它将每组命令视为一个列表,并创建一个新进程来执行每个命令......
我不想错误地解释这一点,所以我想我会在这里问。是否创建子shell一定需要使用这些 () 组命令,还是这只是确保某些命令在同一子销售中运行的一种方法?
也许让我举例问一下。假设我有命令( 中的可执行文件PATH
)a
和b
.在命令提示符下输入以下内容有什么区别吗?
a ; b
(a ; b)
(a) ; (b)
答案1
让a
相等foo=xyz
,并且b
相等echo $foo
。或者更确切地说,让我们将它们定义为函数:
a() { foo=xyz; }
b() { echo $foo; }
然后,让我们尝试您显示的每个变体,在每种情况下,初始化foo
为第一个,并在最后abc
打印 的值。foo
右侧输出:
foo=abc; a ; b; echo $foo
=>xyz
,xyz
foo=abc; (a ; b); echo $foo
=>xyz
,abc
foo=abc; (a) ; (b); echo $foo
=>abc
,abc
因此,在第一个中,分配发生在主级别,因此对脚本的其余部分可见。 (这些函数使用{ ..; }
分组构造,因此它们在主 shell 中运行。)在第二个中,赋值发生在与第一个打印输出相同的子 shell 中,但不会影响脚本的其余部分。在第三个中,赋值发生在第一个子 shell 中,并且仅在该子 shell 中可见,而在脚本的后面部分不可见。
话又说回来,您询问了 中的可执行文件PATH
,并且由于它们无论如何都不会影响 shell 的执行环境,因此它们是否在子 shell 中运行并不重要。即,ls
与 相同(ls)
。但对于 shell 内置函数来说,差异很重要。考虑例如read
(设置变量)或exit
(退出(子)shell)。
命令替换也在子 shell 中运行,因此例如
foo=abc
echo $(foo=xyz; echo $foo)
echo $foo
打印xyz
和abc
.
当然,命令替换语法也使用括号,因此存在一定的对称性。 (话又说回来,(( ... ))
这是完全不同的事情。)
任何与 shell 异步或并发运行命令的操作也必然会启动子 shell,因为这样做需要生成一个无法修改主 shell 进程的新进程。
一个常见的情况是管道。在 中foo | bar | doo
, 和 都foo
在bar
子 shell 中运行,并且doo
可能在子 shell 中运行,也可能在主 shell 环境中运行。
例如
foo=abc
{ foo=xyz; echo $foo; } | cat
echo $foo
印刷xyz
,abc
。
看:为什么我的变量在一个“while read”循环中是本地变量,但在另一个看似相似的循环中却不是?
foo &
显然,使用、 或进程替换 ( <( foo )
) 或其他类似方式在后台显式运行某些内容也会启动子 shell。
无论如何,它( .. )
是为了启动一个子外壳而显式启动一个子外壳的。对于其他人,我们可以说这是一种副作用。
答案2
这三行做了不同的事情。
[me@here foo]$ echo A-$BASHPID ; echo B-$BASHPID
A-534171
B-534171
[me@here foo]$ (echo A-$BASHPID ; echo B-$BASHPID)
A-534798
B-534798
[me@here foo]$ (echo A-$BASHPID) ; (echo B-$BASHPID)
A-534808
B-534809
首先从当前 shell 的上下文中执行 a,然后执行 b(本例中 pid=534171)。第二个创建一个新的子 shell (pid=534798),然后在该新子 shell 中执行 a,然后执行 b。第三个创建一个新的子 shell (pid=534808) 并在其中执行 a。该子 shell 退出后,它会创建另一个子 shell (pid=534809) 并在该子 shell 中执行 b。这些子 shell 通常不会从原始 shell 继承其整个环境(例如未导出的 shell 变量、文件描述符、'ERR、DEBUG、RETURN 陷阱处理是特殊情况),并且子 shell 环境的某些部分被显式更改(进程 IDS、外壳历史...)。在子 shell 中执行的命令通常也不可能修改父 shell 环境。
[me@here foo]$ A=foo
[me@here foo]$ A=bar
[me@here foo]$ (A=baz)
[me@here foo]$ echo $A
bar
甚至二进制可执行文件也是从这些子 shell 启动的可以发现差异并采取不同的行为。