根据我的阅读,将命令放在括号中应该在子 shell 中运行它,类似于运行脚本。如果这是真的,那么如果 x 没有导出,它如何看到变量 x 呢?
x=1
(echo $x)
在命令行上运行结果为 1
echo $x
正如预期的那样,在脚本中运行不会产生任何结果
答案1
子 shell 最初是与原始 shell 进程几乎相同的副本。在底层,shell 调用fork
系统调用1,创建一个新进程,其代码和内存是副本2。创建子 shell 后,它与其父 shell 之间几乎没有什么区别。特别是,它们具有相同的变量。即使$$
特殊变量在子 shell 中也保持相同的值:它是原始 shell 的进程 ID。同样,$PPID
原始 shell 的父级 PID 也是如此。
一些 shell 会更改子 shell 中的一些变量。 Bash ≥4.0 设置BASHPID
为 shell 进程的 PID,该 PID 在子 shell 中发生变化。 Bash、zsh 和 mksh 安排$RANDOM
在父 shell 和子 shell 中产生不同的值。但除了像这样的内置特殊情况外,所有变量在子 shell 中都具有与原始 shell 中相同的值、相同的导出状态、相同的只读状态等。所有函数定义、别名定义、shell 选项和其他设置也会被继承。
创建的子 shell(…)
与其创建者具有相同的文件描述符。创建子 shell 的其他一些方法会在执行用户代码之前修改一些文件描述符;例如,管道的左侧在子 shell 3中运行,标准输出连接到管道。子 shell 也以相同的当前目录、相同的信号掩码等开始。少数例外之一是子 shell 不继承自定义陷阱:忽略的信号 ( ) 在子 shell 中仍然被忽略,但其他陷阱 (trap '' SIGNAL
trap CODE
信号) 重置为默认操作4 .
因此,子 shell 与执行脚本不同。脚本是一个单独的程序。这个单独的程序可能碰巧也是由与父程序相同的解释器执行的脚本,但是这种巧合并没有使单独的程序对父程序的内部数据有任何特殊的可见性。非导出变量是内部数据,因此当子 shell 脚本的解释器是被处决,它看不到这些变量。导出的变量,即环境变量,被传输到执行的程序。
因此:
x=1
(echo $x)
打印,1
因为子 shell 是生成它的 shell 的复制品。
x=1
sh -c 'echo $x'
碰巧将 shell 作为 shell 的子进程运行,但x
第二行上的 与x
第二行上的 的联系并不比
x=1
perl -le 'print $x'
或者
x=1
python -c 'print x'
1 除非 shell 优化分叉,但会根据需要模拟分叉以保留其正在执行的代码的行为。 Ksh93 进行了很多优化,而其他 shell 大多没有。
2 从语义上讲,它们是副本。从实施的角度来看,有很多共享正在进行。
3 对于右侧,取决于外壳。
4 如果您对此进行测试,请注意像$(trap)
可能会报告原始外壳的陷阱。另请注意,许多 shell 在涉及陷阱的极端情况下都存在错误。例如忍者请注意,从 bash 4.3 开始,在“两个子 shell”情况下从嵌套子 shellbash -x -c 'trap "echo ERR at \$BASH_SUBSHELL \$BASHPID" ERR; set -E; false; echo one subshell; (false); echo two subshells; ( (false) )'
运行陷阱,而不是从中间子 shell 运行陷阱 -选项应该将陷阱传播到所有子 shell,但中间子 shell 已被优化掉,所以不是不要在那里运行它的陷阱。ERR
ERR
set -E
ERR
ERR
答案2
显然,是的,正如所有文档所说,带括号的命令是在子 shell 中运行的。
子 shell 继承所有父级变量的副本。不同之处在于,您在子 shell 中所做的任何更改都不会在父 shell 中进行。
ksh 手册页使这一点比 bash 更清晰:
man ksh
:
带括号的命令在子 shell 中执行,而不删除非导出变量。
man bash
:
(
列表)
list 在子 shell 环境中执行(请参阅下面的命令执行环境)。影响 shell 环境的变量分配和内置命令在命令完成后不再有效。
命令执行环境
shell 有一个执行环境,由以下部分组成: [...] 通过变量赋值设置的 shell 参数 [...]。
命令替换、用括号分组的命令以及异步命令在与 shell 环境重复的子 shell 环境中调用,[...]