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

x=1


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

在脚本中运行echo $x不会产生预期的结果

#1 楼

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

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

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

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

因此:

x=1
(echo $x)


打印1,因为子壳是

x=1
sh -c 'echo $x'


可以将shell作为shell的子进程运行,但是第二行的xx不再连接第二行不是

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陷阱传播到所有子外壳,但中间subshel​​l已经过优化,因此无法运行其ERR陷阱。

评论


@Kusalananda编号(x = out;(x = in; echo $ x))

–吉尔斯'所以-不再是邪恶的'
17年4月9日在13:05

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

–吉尔斯'所以-不再是邪恶的'
18年3月3日在22:08

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

–吉尔斯'所以-不再是邪恶的'
18 Mar 4 '18 at 0:07

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

–吉尔斯'所以-不再是邪恶的'
18 Mar 12 '18 at 18:52

@吉尔斯;遗憾的是官方文档有此错误!因此,subshel​​l是一个在父shell环境中执行的子进程,而外部命令执行是一个子进程但具有不同的执行环境,对吗?

–架子
18 Mar 12 '18 at 19:54

#2 楼

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

子shell继承了所有父变量的副本。区别在于,您在子shell中所做的任何更改都不会在父级中进行。

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


man ksh


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




man bash



(列表)


列表是在子shell环境中执行的(请参阅COMMAND
执行环境下面)。变量赋值和影响外壳环境的内置命令
在命令完成后仍然无效。





命令执行环境

Shell具有一个执行环境,该执行环境包括以下内容:
通过变量分配设置的[...] shell参数
[...]。
在子shell环境中调用命令替换,用括号分组的命令以及异步命令
它是shell环境的副本,[...]


评论


这与要执行除内置或Shell函数以外的简单命令时相反,它在包含以下各项的单独执行环境中调用,该环境包含以下各项:·Shell变量和标记为导出的函数,以及为命令导出的变量,在环境中传递(来自同一man bash部分),这解释了为什么如果不导出x,回显$ x-script为什么不打印任何内容。

– Johan E
2014年6月21日23:50