括号是否真的将命令放在子外壳中?


94

根据我的阅读,将命令放在括号中应该将其运行在子外壳中,类似于运行脚本。如果是这样,如果不导出x,它将如何看到变量x?

x=1

(echo $x)在命令行上运行会导致1

echo $x如预期那样,在脚本中运行不会产生任何结果

Answers:


134

子外壳程序开始时与原始外壳程序过程几乎完全相同。在后台,shell调用fork系统调用1,这将创建一个新进程,其代码和内存为副本2。创建子Shell时,它与它的父级之间几乎没有区别。特别是,它们具有相同的变量。甚至$$特殊变量在子Shell中也保持相同的值:它是原始Shell的进程ID。同样$PPID,原始外壳的父代的PID也是如此。

一些外壳程序会更改子外壳程序中的一些变量。Bash设置BASHPID为Shell进程的PID,该PID在子Shell中更改。Bash,zsh和mksh安排$RANDOM在父级和子shell中产生不同的值。但是,除了诸如此类的内置特殊情况外,所有变量在子Shell中的值与原始Shell中的值相同,具有相同的导出状态,相同的只读状态等。所有函数定义,别名定义,Shell选项和其他设置也将被继承。

由创建的子Shell (…)具有与其创建者相同的文件描述符。创建子外壳的其他一些方法在执行用户代码之前会修改一些文件描述符。例如,管道的左侧在子壳体3中延伸,标准输出连接到该管道。子外壳程序也以相同的当前目录,相同的信号掩码等开始。少数例外之一是子外壳程序不继承自定义陷阱:被忽略的信号()在子外壳程序中仍然被忽略,但其他陷阱(SIGNAL)被重置采取默认操作4trap '' SIGNALtrap CODE

因此,子shell与执行脚本不同。脚本是一个单独的程序。该单独的程序也可能恰巧也是由与父程序相同的解释器执行的脚本,但是这种巧合不会使单独的程序对父级的内部数据有任何特殊的可见性。非导出变量是内部数据,因此当执行子外壳脚本的解释器时,看不到这些变量。导出的变量(即环境变量)被传输到执行的程序。

从而:

x=1
(echo $x)

进行打印,1因为子外壳是生成它的外壳的复制。

x=1
sh -c 'echo $x'

碰巧在外壳程序的子进程中运行外壳程序,但是x第二行上的与第二行上的连接没有x

x=1
perl -le 'print $x'

要么

x=1
python -c 'print x'

1 一个例外是ksh93外壳,在该外壳上优化了分叉并且可以模拟其大多数副作用。
2从 语义上讲,它们是副本。从实现的角度来看,正在进行很多共享。
3 对于右侧,取决于外壳。
4 如果您对此进行测试,请注意,诸如此类的内容$(trap)可能会报告原始外壳程序的陷阱。还要注意,许多壳在涉及陷阱的极端情况下都有错误。例如,ninjalj指出,从bash 4.3开始,在“两个子外壳”的情况下,从嵌套子外壳bash -x -c 'trap "echo ERR at \$BASH_SUBSHELL \$BASHPID" ERR; set -E; false; echo one subshell; (false); echo two subshells; ( (false) )'运行ERR陷阱,但不ERR从中间子外壳运行陷阱- set -E选项应传播ERR陷阱到所有子shell,但是中间子shell已被优化,因此无法运行其ERR陷阱。


2
@Kusalananda No.(x=out; (x=in; echo $x)
Gilles

2
@ flow2k这是在同一级别发生的事情的扩展顺序。但是您还需要考虑扩展与评估如何混合。当扩展需要评估嵌套结构时,首先评估内部结构。因此,例如,要评估echo $(x=2; echo $x),片段$(x=2; echo $x)需要扩展。这需要评估命令x=2; echo $x$x在评估零件之后,在此评估期间发生扩展x=2
吉尔斯

2
@ flow2k参数扩展和命令替换之间没有顺序。请注意,该语句使用分号分隔扩展步骤,但参数扩展和命令替换位于同一分号分隔的子句中(是的,它很微妙)。当其中一个零件具有影响另一零件的副作用时,例如(未x设置)echo $(echo foo >somefile)${x-$(cat somefile)}或,顺序就很重要echo $(echo $x),${x=1}
吉尔斯

1
@吉尔斯; 我很困惑。如果子外壳程序与执行脚本不同,那么为什么这样说:运行外壳程序脚本会启动一个新进程,即子外壳程序。?另外,应创建一个子外壳环境作为外壳环境的副本。因此,。/ file将在子shell环境中执行,因此它应继承由变量分配设置的shell参数。
折断

2
@haccks ABS中的定义只是一个近似值,并不是一个很好的定义。这些示例很好,但是该页面的前两行过于简单,以至于它们是错误的。从另一个脚本运行一个脚本会启动一个新进程,该进程不是子shell。在SUS中,定义是正确的(但并不总是很容易理解)。./file不在子shell中执行。另请参见unix.stackexchange.com/q/261638unix.stackexchange.com/a/157962
Gilles

15

显然,是的,正如所有文档所述,带括号的命令在子外壳中运行。

子外壳程序继承了所有父变量的副本。不同之处在于您在子Shell中所做的任何更改都不会在父级中进行。

ksh手册页比bash手册页更加清晰:

man ksh

在子外壳中执行带括号的命令,而不会删除未导出的变量。

man bash

(清单)

该列表在子外壳程序环境中执行(请参见下面的“命令执行环境”)。在命令完成后,影响外壳环境的变量分配和内置命令将保持无效。

命令执行环境

外壳程序具有执行环境,该环境包括以下内容:外壳程序参数,这些变量是通过变量赋值设置的。
在子shell环境中调用命令替换,用括号分组的命令以及异步命令,该子shell环境是shell环境的副本,[...]


3
这与相比When a simple command other than a builtin or shell function is to be executed, it is invoked in a separate execution environment that consists of the following.,后者包含项:(· shell variables and functions marked for export, along with variables exported for the command, passed in the environment来自同man bash一部分),该项解释了为什么echo $x-script如果x不导出则什么也不打印。
Johan E
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.