6

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.

0

1 Answer 1

4

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.

6
  • Excellent answer! Thank you for the detailed information! Very helpful. Commented May 30, 2021 at 16:57
  • Did you by chance mean to write "So builtin did the one thing that was asked."? Also, did you mean to add something at the end of "...hence the new command which bypasses functions (and aliases, since command foo puts foo in a position where aliases are not expanded) and finds ."? Commented May 30, 2021 at 17:02
  • Finally, what does the ksh builtin command do? Commented May 30, 2021 at 17:06
  • Also, good point regarding zsh. It is nice in a certain sense that command and builtin do two separate things for that shell, but also frustrating when trying to implement code that is compatible across shells. (Another user did try to comment on that, but later deleted their answer...I don't recall their name.) Commented May 30, 2021 at 17:10
  • 1
    So zsh added it's command after POSIX defined the behavior of command and it is another reason to doubt in the seriousness of zsh.
    – schily
    Commented May 30, 2021 at 17:29

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .