我知道该命令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
我的理解正确吗?或者,做command
和builtin
做完全相同的事情(如果是这样,为什么要builtin
引入?)?或者,command
和之间还有另一个微妙的区别吗builtin
?
(我尝试在 StackExchange 上寻找答案,但没有找到任何东西,所以如果有人能给我指出合适的答案,我将非常感激。)
更新:
还值得注意的是command
,builtin
“跳过”搜索和使用定义的别名。在 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
。