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

相關內容