考虑以下命令:
exit > /dev/null
exit | cat
在一些 shell(ksh、bash、(d)ash)上,行为是相同的:第一个命令导致 shell 立即退出,而第二个命令没有可见的行为。
我得出的结论是,第一个命令不涉及 fork(2),而第二个命令涉及两个(一个用于执行exit
,另一个用于 execve(2) cat
)。
我查看了 POSIX 规范第 2.14 节,但没有找到任何明确说明这一点的内容。
POSIX 是否指定某些命令不得 fork(2) 而另一些命令必须?标准是否可以接受为第一个命令生成子 shell?
我知道( exit )
永远不应该退出当前 shell,因为括号会生成一个实际执行exit
命令的子 shell,这意味着子 shell 将立即退出。但是,我不确定重定向和管道,因为这里没有括号。
我问这个问题是因为在最近的课程实验室中,我们被告知要实现一个最小的 Unix shell。我们可以实现许多“可选功能”,以供补充。这些“可选功能”之一是组合重定向和管道。我们有一些不同的实现,我相信其中一些比其他实现更接近 POSIX 的指定行为,因此我想知道实际行为是如何指定的。
答案1
出色地 (出口),
这出口实用程序应使外壳退出 当前的执行环境[...]
和 (2.12. Shell执行环境)
子 shell 环境应创建为 shell 环境的副本,[...]此外,每个命令的 多命令管道位于子shell环境中;然而,作为扩展,管道中的任何或所有命令都可以在当前环境中执行。所有其他命令应在 当前的 shell 环境。
因此,exit
管道中的 运行在它自己的执行环境/子 shell 中,并且仅退出,而简单命令中的 则exit > /dev/null
运行在主 shell 环境中。 (正如评论中所指出的,重定向根本不会真正影响它。)
请注意,第二个引号中间的部分意味着某些 shell 可能会运行主环境中管道的所有命令,因此即使在这种情况下也会退出整个 shell。在实践中,更常见的是对管道的最后一个命令执行此操作。
在 Bash 中lastpipe
,例如:
$ bash -c 'true | exit; echo end.'
end.
但
$ bash -O lastpipe -c 'true | exit; echo end.'
不打印任何内容。
答案2
发生这种情况是因为是在 for而不是 for 的exit
子 shell 中执行的。 exit | cat
exit > /dev/null
从子 shell 退出并不从主 shell 退出:
如果当前执行环境是子shell环境,则shell应以指定的退出状态退出子shell环境,并在调用该子shell环境的环境中继续运行
但subshell与not subshell的具体区别在章节中有详细说明POSIX标准的2.12– 完整引用相关段落:
子 shell 环境应创建为 shell 环境的副本,但未被忽略的信号陷阱应设置为默认操作。对子 shell 环境所做的更改不应影响 shell 环境。命令替换、用括号分组的命令以及异步列表应在子 shell 环境中执行。此外,多命令管道中的每个命令都位于子shell环境中;然而,作为扩展,管道中的任何或所有命令都可以在当前环境中执行。所有其他命令应在当前 shell 环境中执行。
这里exit | cat
符合描述:
多命令管道的每个命令都位于子 shell 环境中
也因此会通常在子 shell 中执行。然而,这可能会让你陷入困境:
然而,作为扩展,管道中的任何或所有命令都可以在当前环境中执行
...这意味着不能保证在所有 shell 中都能保证这一点。我之前必须调试代码,其中一个实现在当前 shell 中执行管道的右侧,从而允许以下代码在 KSH 的某些实现上工作,但在其他实现上则不行:
cat foo | while read line
do
X="$line"
done
所以总是假设一个管道可能生成一个子 shell,但不依赖它。