括号真的把命令放在子shell中吗?

Igo*_*rio 126 bash shell-script subshell

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

x=1
Run Code Online (Sandbox Code Playgroud)

(echo $x)在命令行上运行结果为 1

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

Gil*_*il' 169

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

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

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

因此,子shell 与执行脚本不同。脚本是一个单独的程序。巧合的是,这个单独的程序可能也是由与父程序相同的解释器执行的脚本,但这种巧合并没有使单独的程序对父程序的内部数据有任何特殊的可见性。未导出的变量是内部数据,所以当子shell 脚本的解释器被执行时,它看不到这些变量。导出的变量,即环境变量,被传送到执行的程序。

因此:

x=1
(echo $x)
Run Code Online (Sandbox Code Playgroud)

打印1是因为子外壳是产生它的外壳的复制品。

x=1
sh -c 'echo $x'
Run Code Online (Sandbox Code Playgroud)

恰巧运行shell作为外壳的子过程,但x在第二行有与没有更多的连接x,第二行比

x=1
perl -le 'print $x'
Run Code Online (Sandbox Code Playgroud)

或者

x=1
python -c 'print x'
Run Code Online (Sandbox Code Playgroud)

1 一个例外是ksh93分叉被优化出来并且其大部分副作用被模拟的外壳。
2 从 语义上讲,它们是副本。从实施的角度来看,有很多共享正在进行。
3 对于右侧,它取决于外壳。
4 如果您对此进行测试,请注意,诸如此类的内容$(trap)可能会报告原始 shell 的陷阱。还要注意,许多 shell 在涉及陷阱的极端情况下都有错误。例如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陷阱到所有子外壳,但中间子外壳被优化掉了,所以没有运行它的ERR陷阱。

  • @hackks ABS 中的定义是一个近似值,并不是一个很好的定义。这些例子很好,但该页面的前两行过于简单以至于它们是错误的。从另一个脚本运行脚本会启动一个*非*子shell的新进程。在 SUS 中,定义是正确的(但并不总是很容易理解)。`./file` 不在子 shell 中执行。另见 https://unix.stackexchange.com/q/261638 和 https://unix.stackexchange.com/a/157962 (5认同)
  • @Kusalananda 号 (`x=out; (x=in; echo $x)`) (2认同)
  • @flow2k 这是同一级别发生的事情的扩展顺序。但是您还需要考虑扩展与评估是如何混合的。当扩展需要对嵌套构造求值时,首先对内部构造求值。因此,例如,要评估 `echo $(x=2; echo $x)`,需要扩展片段 `$(x=2; echo $x)`。这需要评估命令`x=2; 回声 $x`。在评估部分“x=2”之后,“$x”的扩展发生在此评估期间。 (2认同)
  • @flow2k 参数扩展和命令替换之间没有顺序。请注意,这句话使用分号分隔扩展步骤,但参数扩展和命令替换在同一个分号分隔的子句中(是的,它很微妙)。当其中一个部分具有影响另一部分的副作用时,顺序很重要,例如(未设置 `x`)`echo $(echo foo >somefile)${x-$(cat somefile)}` 或 `echo $ (echo $x),${x=1}`。 (2认同)
  • @吉尔斯;很遗憾官方文档有这个错误!所以,subshel​​l 是在父 shell 环境中执行的子进程,而外部命令执行是一个子进程,但具有不同的执行环境,对吗? (2认同)

Mik*_*kel 18

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

子shell 继承了所有父变量的副本。不同之处在于您在子 shell 中所做的任何更改也不会在父级中进行。

ksh 手册页比 bash 手册页更清楚一点:

man ksh

带括号的命令在子 shell 中执行,而不删除非导出的变量。

man bash

(列表)

list 在子 shell 环境中执行(请参阅下面的命令执行环境)。影响 shell 环境的变量赋值和内置命令在命令完成后不再有效。

命令执行环境

shell 有一个执行环境,它包括以下内容: [...] 通过变量赋值设置的 shell 参数 [...]。
命令替换、用括号分组的命令和异步命令在与 shell 环境相同的子 shell 环境中调用,[...]

  • 这与“当要执行除内置函数或 shell 函数之外的简单命令时,它会在包含以下内容的单独执行环境中调用。”,其中包含项目:`· shell 变量和函数标记为导出,以及为命令导出的变量,在环境中传递(来自同一个 `man bash` 部分),这解释了为什么如果不导出 `x`,`echo $x` 脚本不打印任何内容。 (3认同)