我明白這個命令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
。