Shell 脚本中命令 `command` 与命令 `builtin` 之间的区别

Shell 脚本中命令 `command` 与命令 `builtin` 之间的区别

我知道该命令command是在最新的 POSIX 标准builtin事实并非如此。我还意识到这两个命令都是常规内置函数(即它们可以被用户定义的函数覆盖)。有些 shell 定义了builtin,但不是全部(例如dash没有)。我想了解为什么builtin在某些 shell 中引入。

据我所知,builtin只会返回特殊的内置函数,然后是常规的内置函数,但command会返回特殊的内置函数,然后是常规的内置函数,然后是路径上的命令(并且-p可以使用开关来command指定它使用shell 定义的默认值($PATH以防用户修改$PATH)。

例如,在 中mksh,我看到以下内容:

笔记: mksh从存储库安装在 Ubuntu 20.04 上http://archive.ubuntu.com/ubuntu focal/universe amd64 mksh amd64 58-1

$ echo $KSH_VERSION
@(#)MIRBSD KSH R58 2020/03/27
$ which -a echo
/usr/bin/echo
/bin/echo
$ which -a printf
/usr/bin/printf
/bin/printf
$ type echo
echo is a shell builtin
$ type printf
printf is /usr/bin/printf
$ command echo 'Hello World!'
Hello World!
$ command printf 'Hello World!\n'
Hello World!
$ builtin echo 'Hello World!'
Hello World!
$ builtin printf 'Hello World!\n'
mksh: builtin: printf: not found
$ sudo cp /usr/bin/printf /usr/bin/printf.backup
$ sudo cp /bin/printf /bin/printf.backup
$ sudo rm /usr/bin/printf
$ sudo rm /bin/printf
rm: cannot remove '/bin/printf': No such file or directory
$ sudo cp /usr/bin/printf.backup ~/printf
$ echo $PATH | sed 's/:/\n/g'
/usr/local/sbin
/usr/local/bin
/usr/sbin
/usr/bin
/sbin
/bin
/usr/games
/usr/local/games
# ...remainder ommitted here for brevity
$ export PATH=~:$PATH
$ echo $PATH | sed 's/:/\n/g'
/home/my_username
/usr/local/sbin
/usr/local/bin
/usr/sbin
/usr/bin
/sbin
/bin
/usr/games
/usr/local/games
# ...remainder ommitted here for brevity
$ command printf 'Hello World!\n'
Hello World!
$ command -p printf 'Hello World!\n'
mksh: printf: inaccessible or not found

我的理解正确吗?或者,做commandbuiltin做完全相同的事情(如果是这样,为什么要builtin引入?)?或者,command和之间还有另一个微妙的区别吗builtin

(我尝试在 StackExchange 上寻找答案,但没有找到任何东西,所以如果有人能给我指出合适的答案,我将非常感激。)

更新:

还值得注意的是commandbuiltin“跳过”搜索和使用定义的别名。在 POSIX shell 计算顺序中,别名扩展出现在命令搜索和计算之前,算术、变量和文件通配符扩展也是如此。但是,算术、变量和通配符在command和内计算builtin,但不计算别名。似乎文件应该提到一些内容。

例如:

$ bash --version
GNU bash, version 5.0.17(1)-release (x86_64-pc-linux-gnu)
Copyright (C) 2019 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
$ command echo $((1 + 1))
2
$ builtin echo $((3 + 1))
4

$ bash --version
GNU bash, version 5.0.17(1)-release (x86_64-pc-linux-gnu)
Copyright (C) 2019 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
$ alias \[='echo hello'
$ [
hello
$ if builtin [ 'north' != 'south' ]; then echo 'what a world!'; fi
what a world!

更新2:

我认为还值得注意的是,经过一些挖掘和实验,我发现zsh行为完全相反POSIX 标准和其他 Bourne 风格 shell 与command命令的关系。

来自zsh 文档(第 6.2 节,前置命令修饰符)

命令[-pvV]
命令字被视为外部命令的名称,而不是 shell 函数或内置命令的名称。

例如

$ echo ${ZSH_VERSION}
5.8
$ command cd ~
zsh: command not found: cd
$ command -p cd ~
zsh: command not found: cd
$ command echo 'hi'
hi
# `echo` is a regular builtin just like `cd` though...??!!!
$ set -o |grep posix
posixaliases          off
posixargzero          off
posixbuiltins         off
posixcd               off
posixidentifiers      off
posixjobs             off
posixstrings          off
posixtraps            off
$ cd() { echo 'I told you.'; }
$ cd
I told you.
# ????!!!!

仅当POSIX_BUILTINS设置了环境变量(使用set -o posixbuiltins)时,该命令才会command执行特殊和常规的内置命令。

例如

$ echo ${ZSH_VERSION}
5.8
$ cd /
$ ls
bin   dev  home  lib    lib64   lost+found  mnt  proc  run   snap  sys  usr
boot  etc  init  lib32  libx32  media       opt  root  sbin  srv   tmp  var
$ set -o posixbuiltins
$ command cd ~
$ ls
Desktop    Downloads  Pictures  Templates
Documents  Music      Public    Videos
$ command -p cd /
$ ls
bin   dev  home  lib    lib64   lost+found  mnt  proc  run   snap  sys  usr
boot  etc  init  lib32  libx32  media       opt  root  sbin  srv   tmp  var

另一方面,从bash 文档

命令
命令[-pVv]命令[参数...]

使用参数运行命令,忽略任何名为 command 的 shell 函数。仅执行 shell 内置命令或通过搜索 PATH 找到的命令。

例如

$ bash --version
GNU bash, version 5.0.17(1)-release (x86_64-pc-linux-gnu)
Copyright (C) 2019 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
$ cd /
$ ls
bin   dev  home  lib    lib64   lost+found  mnt  proc  run   snap  sys  usr
boot  etc  init  lib32  libx32  media       opt  root  sbin  srv   tmp  var
$ set +o posix  # Turn off POSIX mode
$ command cd ~
$ ls
Desktop    Downloads  Pictures  Templates
Documents  Music      Public    Videos
$ command -p cd /
$ ls
bin   dev  home  lib    lib64   lost+found  mnt  proc  run   snap  sys  usr
boot  etc  init  lib32  libx32  media       opt  root  sbin  srv   tmp  var

所以zsh行为完全相反关于command命令的其他 Bourne 风格 shell...我开始zsh越来越不喜欢...买家要小心(买者自负) 我想。

更新3:

还值得注意的是,它ksh88没有command内置命令。这是在 中引入的ksh93。要替换 中的内置函数ksh88,您将不得不使用别名、函数和引用的尴尬组合。

资料来源:罗宾斯、阿诺德;罗森布拉特、比尔.学习 Korn Shell:Unix 编程(第 456 页)。奥莱利媒体。 Kindle版。

这与@Gilles“所以-停止邪恶”的答案是一致的。

答案1

POSIX 理由command回答了您问题的大部分历史方面。

命令实用程序有点类似于第八版 shell内置命令,但自从命令还去文件系统搜索实用程序,名称内置不直观。

(…) 这命令 -v-V添加选项是为了满足当前由三个不同的历史实用程序完成的用户需求:类型在 System V shell 中,何处在 KornShell 中,以及哪个在 C 外壳中。

在里面第八版builtin内置函数被记录为只是绕过函数:

执行内置的特殊命令(例如break),无论定义的同名函数如何。

别名还不存在(当它们出现时,有不同的机制可以绕过它们)。如果您想绕过某个函数来执行外部命令,您可以给出其完整路径,这样做的优点是可以准确指定您想要执行的内容,以防命令搜索路径上有多个具有该名称的可执行文件。命令的完整路径可能不同的系统间的可移植性并不是一个普遍的问题。builtin确实无法用其他方式完成的一件事也是如此。

后来,POSIX 出现并添加了绕过函数的标准方法。在这种情况下,外部命令位于不同位置的系统的可移植性非常令人担忧,因此builtin还不够,因此新的command绕过函数(和别名,因为command foo放置foo在别名不扩展的位置)并找到标准命令。 (今天 ksh 有一个内置命令builtin,它做了一些完全不同的事情,但我不知道它是在 POSIX 创建之前还是之后出现的command。)但是,command出于可移植性考虑,故意不跳过内置命令:如果 sh当程序调用标准命令时,操作系统可以选择是否将此命令作为内置命令提供。command再次取消特殊内置函数的“特殊内置”行为,以便应用程序不需要知道它是否正在调用内置函数。

我不知道为什么 zshcommand在不在 POSIX 模式下时会绕过内置函数(特别是当posix_builtins选项未设置)。它当前的实施command可以追溯到 1996 年 5 月发布的更改zsh 2.6 测试版 20“从保留字列表中删除 -、exec、noglob 和 command”)。由于该实现已经对 POSIX 模式进行了不同的处理,我认为它是为了向后兼容早期版本的 zsh,但我没有进一步调查。这可能是故意的,因为如果posix_builtins未设置,内置命令不一定与 POSIX 兼容,因此如果应用程序使用特定的 POSIX 命令,最好不要调用它们command

相关内容