Shell腳本中命令“command”與“builtin”命令之間的區別
我了解該命令
command
是在最新的 POSIX 標準中指定的,但builtin
不是。我還意識到這兩個命令都是正常的內置命令(即它們可以被使用者定義的函式覆蓋)。一些 shell 定義builtin
了 ,但不是全部(例如dash
沒有)。我想了解為什麼builtin
在某些 shell 中引入。據我所知,
builtin
只會返回特殊的然後是正常的內置函式,但command
會返回特殊的內置函式,然後是正常的內置函式,然後是路徑上的命令(並且-p
可以使用開關command
來指定它使用$PATH
如果使用者修改了$PATH
) ,則為 shell 定義的預設值。例如,在 中
mksh
,我看到以下內容:(注意:
mksh
從 repo 安裝在 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:
I think it’s also worth noting that, after some digging and experimenting, I found that
zsh
behaves THE COMPLETE OPPOSITE of the POSIX standard and other Bourne style shells with regard to thecommand
command.From the zsh docs (Section 6.2, Precommand Modifiers)
command $$ -pvV $$
The command word is taken to be the name of an external command, rather than a shell function or builtin.
For example
$ 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. # ????!!!!
Only if the
POSIX_BUILTINS
environment variable is set (useset -o posixbuiltins
) will the commandcommand
then also execute special and regular builtins.For example
$ 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
On the flip side, from the bash docs
command
command
$$ -pVv $$ command $$ arguments … $$ Runs command with arguments ignoring any shell function named command. Only shell builtin commands or commands found by searching the PATH are executed.
For example
$ 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
So
zsh
behaves THE COMPLETE OPPOSITE of other Bourne-style shells with regard to thecommand
command…I am beginning to dislikezsh
more and more…buyer beware (caveat emptor) I suppose.UPDATE 3:
It’s also worth noting that
ksh88
does not have acommand
built-in command. This was introduced inksh93
. To replace a built-in inksh88
, you would have to use an awkward combination of aliasing, functions, and quoting.(Source: Robbins, Arnold; Rosenblatt, Bill. Learning the Korn Shell: Unix Programming (p. 456). O’Reilly Media. Kindle Edition.)
This is consistent with @Gilles ‘SO- stop being evil’’s answer.
The POSIX rationale for
command
answers most of the historical aspects your question.The command utility is somewhat similar to the Eighth Edition shell builtin command, but since command also goes to the file system to search for utilities, the name builtin would not be intuitive.
(…) The command -v and -V options were added to satisfy requirements from users that are currently accomplished by three different historical utilities: type in the System V shell, whence in the KornShell, and which in the C shell.
In the Eighth Edition sh, the
builtin
builtin was documented as just bypassing functions:Execute the built-in special command (such as break) regardless of functions defined with the same name.
Aliases didn’t exist yet (and when they appeared, there were different mechanisms to bypass them). If you wanted to bypass a function to execute an external command, you could give its full path, which had the advantage of specifying exactly what you wanted to execute in case there were multiple executables with that name on the command search path. Portability across systems where the full path to a command might be different wasn’t a widespread concern. So
builtin
did the one thing that couldn’t really be done another way.Later, POSIX came and added a standard way of bypassing a function. In this context, portability to systems where external commands were in different locations was very much a concern, so
builtin
wasn’t enough, hence the newcommand
which bypasses functions (and aliases, sincecommand foo
putsfoo
in a position where aliases are not expanded) and finds standard commands. (Also today ksh has a builtin calledbuiltin
that does something completely different, but I don’t know if it came before or after POSIX createdcommand
.) However,command
purposefully does not skip built-in commands, again due to portability concerns: if an sh programs invokes a standard command, it’s the operating system’s choice whether this command might be provided as a built-in.command
cancels the “special builtin” behavior of special builtins, again so that the application doesn’t need to know whether it’s invoking a builtin or not.I don’t know why zsh’s
command
bypasses builtins when not in POSIX mode (specifically, when theposix_builtins
option is unset). Its current implementation ofcommand
dates back to a change in May 1996 released in zsh 2.6 beta 20 (“remove -, exec, noglob and command from the list of reserved words”). Since that implementation already had different handling for POSIX mode, I presume it was for backward compatibility with an earlier version of zsh, but I haven’t investigated further. It might be deliberate because ifposix_builtins
is unset, built-ins are not necessarily compatible with POSIX and so it’s best to not invoke them if an application uses the specifically POSIXcommand
command.