![这是一个“仿佛”规则。](https://linux22.com/image/146862/%E8%BF%99%E6%98%AF%E4%B8%80%E4%B8%AA%E2%80%9C%E4%BB%BF%E4%BD%9B%E2%80%9D%E8%A7%84%E5%88%99%E3%80%82.png)
从关于 printf 是否是 yash 内置的这个问题, 来了这个答案引用了 POSIX 标准。
答案指出,POSIX 搜索序列是查找所需命令的外部实现,然后,如果 shell 已将其实现为内置命令,则运行该内置命令。 (对于不支持的内置函数特殊内置.)
为什么 POSIX 要求在允许运行内部实现之前存在外部实现?
看起来……随意,所以我很好奇。
答案1
这是一个“仿佛”规则。
简而言之:如果实现决定将标准外部命令也作为 shell 内置命令提供,则用户所看到的 shell 行为不应改变。
我展示的对比https://unix.stackexchange.com/a/496291/5132(一方面)PD Korn、MirBSD Korn 和 Heirloom Bourne shell 的行为之间; (另一方面)Z、93 Korn、Bourne Again 和 Debian Almquist shell; (在紧握的手上)渡边贝壳凸显了这一点。
对于没有printf
内置的 shell,删除/usr/bin
会PATH
调用printf
停止工作。 Watanabe shell 在其一致性模式下表现出的 POSIX 一致性行为会导致相同的结果。具有printf
内置功能的shell 的行为是仿佛它正在调用外部命令。
/usr/bin
然而,如果从 中删除,所有不合格 shell 的行为都不会改变PATH
,而且它们确实会改变不是行为就好像它们正在调用外部命令一样。
该标准试图向您保证的是,shell 可以内置各种通常的外部命令(或将它们实现为自己的 shell 函数),并且您仍然可以从内置命令中获得与您所做的相同的行为如果您进行调整PATH
以阻止发现命令, 则可以使用外部命令。PATH
仍然是您选择和控制可以调用的命令的工具。
(正如所解释的https://unix.stackexchange.com/a/448799/5132,几年前,人们通过改变正在播放的内容来选择他们的 Unix 的个性PATH
。)
有人可能会认为发出命令总是工作不管是否可以找到PATH
事实上是重点使通常的外部命令内置。 (printenv
事实上,这就是为什么我的 nosh 工具集刚刚在 1.38 版本中获得了内置命令。尽管这是不是如地狱。)
但该标准保证您会看到相同的常规外部命令的行为不是PATH
从 shell 启动的,正如您从调用该execvpe()
函数的其他非 shell 程序中看到的那样,并且 shell 不会神奇地运行其他程序无法使用相同命令找到的(显然)普通外部命令PATH
。从用户的角度来看,一切都自洽地工作,并且PATH
是控制其工作方式的工具。
进一步阅读
答案2
这是相当荒谬的,这就是为什么没有 shell 在默认模式下实现它。
该标准的理由及其说明例子表明这是一次拙劣的尝试常规的与路径关联的内置函数,并让用户通过在其之前出现自己的二进制文件来覆盖它PATH
(例如,printf
与 关联的内置函数/usr/bin/printf
可以/foo/bin/printf
通过设置 被外部命令覆盖PATH=/foo/bin:$PATH
)。
然而,该标准最终并没有要求这一点,而是完全要求不同的(而且也是无用且意想不到的)。
您可以在此处阅读更多相关信息错误报告。引用自最终接受的案文:
许多现有的实现执行常规内置而不执行 PATH 搜索。此行为与规范文本不匹配,并且不允许脚本作者通过特制的 PATH 覆盖常规内置实用程序。此外,其基本原理还解释说目的是让作者覆盖 通过修改 PATH 来内置,但是这不是规范文本所说的。
FWIW,我认为也没有任何 shell 实现已接受文本中的修改后的要求。
答案3
后续对比echo
:printf
(以下, builtin
意思是“特殊内置”,“常规内置”不被我认为是内置,因为它们没有内置到外壳中)
第一个 POSIX 标准化委员会无法就如何标准化 echo 达成一致,因此他们妥协地发布了如果传递标志(-e,-n,-E
等)或任何参数包含转义序列(\n,\c,\t
等),则该行为将由执行 shell 而不是 POSIX。相反,该printf
命令被添加并给出了明确定义的行为。 (来源:经典 Shell 脚本,作者:Robbins 和 Beebe)。
尽管printf
定义明确,但某些 shell 没有printf
内置命令(例如mksh
)。相反,他们使用printf
from /usr/bin/
。这意味着从该 shell 运行的所有脚本都将在给定操作系统(Ubuntu、Fedora 等)上打印相同的内容,但它们不一定会跨操作系统打印相同的内容(事实上,许多用户 为此更改了printf
他们的/usr/bin
原因)。
或者,无论操作系统如何,具有 as 内置功能的 shellprintf
都会打印相同的内容,但前提是用作 shell 的实现。然而,由于printf
行为是由 POSIX 标准定义的,因此程序员不必担心这一点。但是,如果对使用fromPATH
的 shell 进行覆盖,则不会找到。printf
/usr/bin/
printf
尽管所有 shell 都具有echo
内置功能,但有些 shell 直接解释转义序列(例如ash
),而其他 shell(大多数)则需要一个-e
标志:该行为不是由 POSIX 定义,而是由 shell 定义。
echo
vs.的主要烦恼之一printf
是echo
默认在字符串末尾打印新行,但printf
实际上却没有。printf
需要\n
转义序列来打印新行。相反,为了防止
echo
打印新行,\c
需要转义序列(可能还需要标志-e
)。
printf
建议最大程度地提高可移植性,因为它的行为是由 POSIX 定义的,但我个人发现在每行末尾显式打印一个新行非常烦人(我编写的大多数行都需要在末尾添加一个新行,而且我很少需要抑制echo
打印新行)。另一方面,
echo
它始终可用,因为它是内置的(没有在 $PATH 上找不到的风险),并且可以执行简单的检查来确定是否-e
需要该标志并创建相应的别名echo
:
#! /bin/sh -
# Determine if "builtin" command exists.
BUILTIN='builtin'
if ! ("${BUILTIN}" echo 123 >/dev/null 2>&1); then
BUILTIN=''
fi
export BUILTIN
ECHO='echo -e'
if ${BUILTIN} [ "`echo -e test`" = '-e test' ]; then
ECHO='echo'
fi
export ECHO
# Now use "${ECHO}" where you would normally use "echo"...
就我个人而言,我更喜欢这样做,并且仅printf
在需要特殊格式时才使用。
更新:
我应该在应得的信用处给予适当的信用。上面的shell代码直接取自shunit2
.这要归功于 Kate Ward 和shunit2
开发团队! (做得好 ;) )
答案4
也添加这个(经典 Shell 脚本罗宾斯和毕比是一个伟大的书):
shell 有许多内置命令。这意味着 shell 本身执行命令,而不是在单独的进程中运行外部程序。此外,POSIX 区分“特殊”内置命令和“常规”内置命令。[大多数常规内置命令] 必须内置才能使 shell 正常运行(例如
read
)。其他命令通常只是为了提高效率而内置到 shell 中(例如true
和false
)。标准还允许内置其他命令以提高效率,但所有常规内置程序都必须可以作为单独的程序进行访问,并且可以由其他二进制程序直接执行。当 shell 搜索要执行的命令时,特殊内置命令和常规内置命令之间的区别就开始发挥作用。命令搜索顺序是首先是特殊内置命令,然后是 shell 函数,然后是常规内置命令,最后是通过搜索 中列出的目录找到的外部命令$PATH
。这种搜索顺序使得定义扩展或覆盖常规 shell 内置函数的 shell 函数成为可能。此功能最常用于交互式 shell。例如,假设您希望 shell 的提示符包含当前目录路径名的最后一个组成部分。实现这一点的最简单方法是PS1
每次更改目录时都更改 shell。您可以cd
[为此]编写自己的[]函数。美中不足的是这里有一只小苍蝇。 shell 函数如何访问“真实”命令的功能cd
?...需要的是一个“逃生舱口”,告诉 shell 绕过函数搜索并访问真实命令。这是command
内置命令的工作。[但是] 该
command
命令不是特殊的内置命令!定义名为命令的函数的 shell 程序员会很倒霉!POSIX 标准为特殊内置命令提供了以下两个额外的特殊特性:
- 特殊内置实用程序中的语法错误可能会导致执行该实用程序的 shell 中止,而常规内置实用程序中的语法错误不应导致执行该实用程序的 shell 中止。如果遇到语法错误的特殊内置实用程序没有中止 shell,则其退出值应为非零。
- 使用特殊内置实用程序指定的变量分配在内置完成后仍然有效;常规内置实用程序或其他实用程序不会出现这种情况。 [也就是说]你可以在命令前面指定变量赋值,并且该变量仅在执行命令的环境中具有该值,而不会影响当前shell或后续命令中的变量。 (例如
PATH=/bin:/usr/bin: awk '...'
)但是,当这样的赋值与特殊内置命令一起使用时,即使在特殊内置命令完成之后,赋值从那时起仍然有效。
阿诺德·罗宾斯和纳尔逊·HF·毕比。经典 Shell 脚本:释放 Unix 力量的隐藏命令(第 262-5 页)。奥莱利媒体。 Kindle版。
请注意,该command
命令使 shell 将指定的命令和参数视为简单命令,从而抑制 shell 函数查找。来自IBM 文档
通常,当命令前面没有 /(斜杠)(表示特定路径)时,shell 通过搜索以下类别来定位命令:
特殊 shell 内置函数 shell 函数 常规 shell 内置函数 PATH 环境变量 例如,如果存在与常规内置函数同名的函数,系统将使用该函数。 command命令允许你调用与函数同名的命令并得到简单的命令。
command -v 和 command -V 命令将 shell 将使用的路径名以及 shell 如何解释命令类型(内置、函数、别名等)写入标准输出。由于 -v 和 -V 标志生成与当前 shell 环境相关的输出,因此该命令作为 Korn shell 或 POSIX shell 常规内置命令提供。 /usr/bin/command 命令可能不会产生正确的结果,因为它是在子 shell 或单独的命令执行环境中调用的。在以下示例中,shell 无法识别别名、子例程或特殊 shell 命令:
(PATH=foo 命令 -v) nohup 命令 -v
因此,在我之前的示例中,我使用了 bashbuiltin
而不是,command
因为如果我将它放在子 shell 中,它就无法正常工作。
我赞同@mosvy:看来标准和规范文本不匹配(确实很荒谬)。