In V7 Unix — where the Bourne shell made its debut — [
was called test
, and it existed only as /bin/test
. So, code you would write today as:
if [ "$foo" = "bar" ] ; then ...
you would have written instead as
if test "$foo" = "bar" ; then ...
This second notation still exists, and I find that it's more clear about what's going on: you are calling a command called test
, which evaluates its arguments and returns an exit status code that if
uses to decide what to do next. That command may be built into the shell, or it may be an external program.¹
[
as an alternative to test
came later.² It may be a builtin synonym for test
, but it is also provided as /bin/[
on modern systems for shells that do not have it as a builtin.
[
and test
may be implemented using the same code. This is the case for /bin/[
and /bin/test
on OS X, where these are hard links to the same executable.³ As a result, the implementation completely ignores the trailing ]
: it doesn't require it if you call it as /bin/[
, and it doesn't complain if you do provide it to /bin/test
.⁴
None of that history affects [[
, because there never was a primordial program called [[
. It exists purely inside those shells that implement it as an extension to the POSIX shell.
Part of the distinction between "builtin" and "keyword" is due to this history. It also reflects the fact that the syntax rules for parsing [[
expressions is different, as pointed out in John1024's answer.⁵
Footnotes:
When you look at it that way, it makes it clear why you must put spaces around [
in shell scripts, unlike the way parentheses and brackets work in most other programming languages. If the shell's command parser allowed if["$x"...
, it would also have to allow iftest"$x"...
It happened around 1980. /bin/[
doesn't exist in my copy of Ancient Unix V7 from 1979, nor does man test
document it as an alias. In the corresponding man page entry I have in a pre-release copy of the System III manual from 1980, it is listed.
ls -i /bin/[ /bin/test
But don't count on this behavior. The Bash built-in version of [
does require the closing ]
, and its built-in test
implementation will complain if you do provide it.
The builtin vs external command distinction may also matter for another reason: the two implementations may behave differently. This is the case for echo
on many systems. Because there is only one implementation, no such distinction needs to be made for a keyword.
if "[" $x -eq 3 ]
works as expected (because Bash looks for the command called[
, and this exists), butif "[[" $x -eq 3 ]]
does not work (because once again Bash searches for a command of the appropriate name, but there is no[[
command). – Kyle Strand Feb 10 '15 at 17:24/usr/bin/echo
, but that doesn't mean it isn't a builtin. – Jonathon Reinhart Feb 12 '15 at 06:56