Command

Shell腳本中命令“command”與“builtin”命令之間的區別

  • May 30, 2021

我了解該命令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

我的理解正確嗎?或者,做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:

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 the command 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 (use set -o posixbuiltins) will the command command 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 the command command…I am beginning to dislike zsh more and more…buyer beware (caveat emptor) I suppose.

UPDATE 3:

It’s also worth noting that ksh88 does not have a command built-in command. This was introduced in ksh93. To replace a built-in in ksh88, 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 new command which bypasses functions (and aliases, since command foo puts foo in a position where aliases are not expanded) and finds standard commands. (Also today ksh has a builtin called builtin that does something completely different, but I don’t know if it came before or after POSIX created command.) 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 the posix_builtins option is unset). Its current implementation of command 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 if posix_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 POSIX command command.

引用自:https://unix.stackexchange.com/questions/651980