关于带有重定向和/或管道的内置命令行为的 POSIX 规范是什么?

关于带有重定向和/或管道的内置命令行为的 POSIX 规范是什么?

考虑以下命令:

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 | catexit > /dev/null从子 shell 退出并不从主 shell 退出:

如果当前执行环境是子shell环境,则shell应以指定的退出状态退出子shell环境,并在调用该子shell环境的环境中继续运行

但subshel​​l与not subshel​​l的具体区别在章节中有详细说明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,但不依赖它。

相关内容