シェルスクリプトにおけるコマンド `command` とコマンド `builtin` の違い

シェルスクリプトにおけるコマンド `command` とコマンド `builtin` の違い

コマンドcommand最新のPOSIX標準builtin両方のコマンドが通常の組み込み(つまり、ユーザー定義関数によって上書きされる可能性があります)。一部のシェルでは が定義されていますbuiltinが、すべてではありません (たとえば、は定義されていません)。一部のシェルで が導入されたdash理由を理解したいと思います。builtin

私の理解する限りでは、 はbuiltin特殊な組み込みコマンドのみを返し、次に通常の組み込みコマンドcommandを返しますが、 は特殊な組み込みコマンド、次に通常の組み込みコマンド、次にパス上のコマンドを返します (また、スイッチを とともに使用して、ユーザーが を変更した場合にシェル定義のデフォルトを使用するように指定-pできます)。command$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の間には別の微妙な違いがありますか?commandbuiltin

(StackExchange で回答を検索してみましたが、何も見つからなかったので、適切な回答を教えていただけると大変助かります。)

アップデート:

commandまた、とbuiltinは定義済みのエイリアスの検索と使用を「スキップ」することにも注意してください。エイリアスの展開は、POSIX シェルの評価順序では、算術、変数、およびファイルのワイルド カード展開と同様に、コマンドの検索と評価の前に行われます。ただし、算術、変数、およびワイルド カードは と 内で評価されますcommandbuiltin、エイリアスは評価されません。ドキュメントで言及する必要があるようです。

例えば:

$ 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 スタイルのシェルの仕様に準拠していますcommand

からzsh ドキュメント(セクション6.2、プレコマンド修飾子)

コマンド [-pvV]
コマンド ワードは、シェル関数や組み込みコマンドではなく、外部コマンドの名前として扱われます。

例えば

$ 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 という名前のシェル関数を無視して、引数付きでコマンドを実行します。シェルの組み込みコマンドまたは 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振る舞う全く逆他の Bourne スタイルのシェルのコマンドに関してはcommand...私はzshますます嫌いになってきています...購入者は注意してください (買い手責任負担) たぶん。

更新3:

には組み込みコマンドksh88がないことにも注意してください。これは で導入されました。 の組み込みコマンドを置き換えるには、エイリアス、関数、引用符の厄介な組み合わせを使用する必要があります。commandksh93ksh88

出典: Robbins, Arnold、Rosenblatt, Bill. 『Learning the Korn Shell: Unix Programming』(p. 456)。O'Reilly Media。Kindle 版。

これは、@Gilles の「だから、邪悪なことはやめなさい」という回答と一致しています。

答え1

POSIXの根拠commandあなたの質問の歴史的な側面のほとんどに答えます。

指示ユーティリティは第8版のシェルに多少似ている組み込みコマンドですが、指示また、ユーティリティを検索するためにファイルシステムにアクセスすると、名前組み込み直感的ではないでしょう。

(…)指示 -vそして-Vオプションは、現在 3 つの異なる従来のユーティリティによって実現されているユーザーからの要件を満たすために追加されました。タイプSystem Vシェルでは、どこからKornShellでは、どれのC シェルで。

の中に第8版sh組み込みbuiltin関数は単に関数をバイパスするものとして文書化されていました。

同じ名前で定義された関数に関係なく、組み込みの特殊コマンド (break など) を実行します。

エイリアスはまだ存在していませんでした (エイリアスが登場したときも、それをバイパスするさまざまなメカニズムがありました)。外部コマンドを実行する関数をバイパスしたい場合は、そのフル パスを指定できました。これには、コマンド検索パスにその名前の実行ファイルが複数ある場合に、実行する内容を正確に指定できるという利点がありました。コマンドのフル パスが異なる可能性があるシステム間での移植性は、あまり大きな問題ではありませんでした。builtin他の方法では実際に実行できない 1 つのことも同様でした。

その後、POSIX が登場し、関数をバイパスする標準的な方法が追加されました。この文脈では、外部コマンドが異なる場所にあるシステムへの移植性が大きな懸念事項であったため、 だけではbuiltin不十分でした。そのため、command関数 (およびエイリアス。 はエイリアスが展開されない位置に配置するcommand fooため) をバイパスし、標準コマンドを検索する新しい が導入されました。(また、現在 ksh には、まったく異なることを行う という組み込みコマンドがありますが、これが POSIX によって が作成される前か後かはわかりません。) ただし、は意図的に組み込みコマンドをスキップしません。これも移植性の問題によるものです。sh プログラムが標準コマンドを呼び出す場合、このコマンドを組み込みとして提供するかどうかはオペレーティング システムの選択です。 は、特殊な組み込みコマンドの「特殊な組み込み」動作をキャンセルします。これもまた、アプリケーションが組み込みコマンドを呼び出しているかどうかを知る必要がないようにするためです。foobuiltincommandcommandcommand

POSIXモードではないときにzshが組み込み関数をバイパスする理由がわかりませんcommand(具体的には、posix_builtinsオプション未設定)。現在の実装はcommand1996年5月の変更に遡り、zsh 2.6 ベータ 20「予約語リストから -、exec、noglob、command を削除します」)。その実装では既に POSIX モードの異なる処理が行われていたため、zsh の以前のバージョンとの下位互換性のためだったと推測しますが、それ以上の調査は行っていません。posix_builtinsが設定されていない場合、組み込みコマンドは必ずしも POSIX と互換性があるわけではないため、アプリケーションが特に POSIX コマンドを使用する場合は組み込みコマンドを呼び出さない方がよいため、意図的である可能性がありますcommand

関連情報