在POSIX术语中,子Shell环境与Shell Execution Environment的概念相关联。
子shell环境是作为父环境的副本创建的单独的shell执行环境。执行环境包括打开的文件,umask,工作目录,shell变量/函数/别名等内容。
对该子外壳环境的更改不会影响父环境。
传统上,在POSIX规范所基于的Bourne shell或ksh88中,这是通过派生子进程来完成的。
POSIX要求或允许命令在子shell环境中运行的区域是传统上ksh88派生子shell进程的区域。
但是,它不会强制实现为此使用子进程。
Shell可以选择以自己喜欢的任何方式来实现该单独的执行环境。
例如,ksh93通过保存父执行环境的属性并在子shell环境终止时在可以避免分叉的上下文中恢复属性来实现(因为在大多数系统上,分叉的优化非常昂贵)。
例如,在:
cd /foo; pwd
(cd /bar; pwd)
pwd
POSIX确实要求必须cd /foo
在单独的环境中运行,并且输出类似以下内容:
/foo
/bar
/foo
它不需要在单独的进程中运行。例如,如果stdout变成断线,则pwd
在subshell环境中运行很可能会将SIGPIPE发送到唯一的shell进程。
包括在内的大多数shell bash
都将通过评估(...)
子进程内部的代码(而父进程等待其终止)来实现它,但是ksh93将改为在(...)
同一进程中运行内部代码时:
- 记住它是在subshell环境中。
- 在后
cd
,保存先前的工作目录(通常在使用O_CLOEXEC打开的文件描述符中),保存OLDPWD,PWD变量的值以及所有cd
可能修改的内容,然后执行chdir("/bar")
- 从子Shell返回后,将还原当前的工作目录(
fchdir()
在保存的fd中带有),并且子Shell可能已修改的所有其他内容。
在某些情况下,无法避免子进程。ksh93无法派生:
var=$(subshell)
(subshell)
但是在
{ subshell; } &
{ subshell; } | other command
也就是说,在这种情况下,事情必须在单独的进程中运行,以便它们可以并行运行。
ksh93优化远不止于此。例如,在
var=$(pwd)
大多数shell会派生一个进程,让子进程将pwd
其stdout重定向到管道来运行命令,pwd
将当前工作目录写入该管道,而父进程在管道的另一端读取结果ksh93
,而不会虚拟化所有内容需要叉子或管子。叉子和管道将仅用于非内置命令。
请注意,除了子外壳,还有其他上下文,这些外壳为其子进程派生了子外壳。例如,要运行存储在单独的可执行文件中的命令(并且该脚本不是用于同一外壳解释器的脚本),外壳将必须派生一个进程来在其中运行该命令,否则它将不会能够在该命令返回后运行更多命令。
在:
/bin/echo "$((n += 1))"
那不是子shell,该命令将在当前shell执行环境中评估,当前shell执行环境的n
变量将增加,但是shell将派生一个子进程以/bin/echo
在其中执行$((n += 1))
as 命令,并扩展as参数。
许多shell实现了一种优化,因为它们不是脚本或子shell的最后一个命令(对于那些作为子进程实现的子shell),它们不会派生一个子进程来运行该外部命令。(bash
但是,仅当该命令是子shell的唯一命令时才这样做)。
这意味着,对于那些shell,如果子shell中的最后一个命令是外部命令,则该子shell不会导致产生额外的进程。如果您比较:
a=1; /bin/echo "$a"; a=2; /bin/echo "$a"
与
a=1; /bin/echo "$a"; (a=2; /bin/echo "$a")
将会创建相同数量的进程,只是在第二种情况下,第二个fork才更早完成,因此a=2
可以在子shell环境中运行。