I understand the command command
is specified in the latest POSIX standard and builtin
is not. I also realize that both commands are regular builtins (i.e. they can be overwritten by user-defined functions). Some shells define builtin
, but not all (e.g. dash
does not). I want to understand why builtin
was introduced in some shells.
To the best of my understanding, builtin
will only return special and then regular built-ins, but command
will return special built-ins, then regular built-ins, then commands on the path (and the -p
switch can be used with command
to specify it to use the shell-defined default $PATH
in case the user has modified the $PATH
).
For example, in mksh
, I see the following:
(NOTE: mksh
installed on Ubuntu 20.04 from repo 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
Is my understanding correct? Or, do command
and builtin
do exactly the same thing (and if so, why was builtin
introduced?)? Or, is there another subtle difference between command
and builtin
?
(I've tried searching for an answer on StackExchange, but haven't found anything, so if anyone can point me to a suitable answer I'd be much obliged.)
UPDATE:
It's also worth noting that command
and builtin
"skip" searching for and using defined aliases. Alias expansion comes before command searching and evaluation, as do arithmetic, variable, and file wild card expansion, in the POSIX shell evaluation order. However, arithmetic, variable, and wild cards are evaluated within command
and builtin
, but not aliases. Seems like something the documents should mention.
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.
$ command echo $((1 + 1))
2
$ builtin echo $((3 + 1))
4
but
$ 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!
UPDATE 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.