2023-11-23
Unix's 'test' program and the V7 Bourne shell
Recently I read Julio Merino's test, [, and [[ (via), which is in part
about there being a real '[
' binary and a 'test
' binary to go
along with it, and as part of that, Merino wonders why the name
'test
' exists at all. I don't have any specific insight into this,
but I can talk a bit about the history, which turns out to be more
tangled and peculiar than I thought.
The existence of 'test' goes back to V7 Unix, which is also where
the Bourne shell was introduced. In V7, the manual page for the
program is test(1), which
has no mention the '[' alternate name, and the source is cmd/test.c,
which has a comment at the start about the '[' usage and code to
support it. While 'test' is a much easier name to deal with in Unix
than '[', there seems to be more to this than just convenience.
There are a number of shell scripts, Makefiles, and so on in V7
Unix, and as far as I can tell all of them use 'test
' and none
of them use '[
'.
(For example, bin/nohup, bin/calendar, bin/lookbib, and usr/src/cmd/learn/makefile.)
Another source of information is S. R. Bourne's An Introduction
to the Unix Shell (also
PDF version
and the V7 troff sources). In
section 2.5, Bourne introduces the 'test
' command under that name,
and then goes on to use it with 'while
' (section 2.6) and 'if
'
(section 2.7). As far as I can see there's no mention of the '[
'
alternate name.
In trawling through various sources of information, I can't actually
find any clear sign that V7 ever had a '[
' hard link for 'test
'.
The test source code is definitely ready for this, but such a hard
link doesn't exist. 4BSD has a src/cmd/DESTINATIONS
file that suggests that /usr/bin/[ existed at this point (along
side /usr/bin/test), but that's the earliest trace I could find.
In 4.1c BSD we finally have clear evidence of /usr/bin/[ in the
form of src/bin/Makefile,
which explicitly creates it as a hard link to /usr/bin/test.
However, there's something rather interesting in the V7 Bourne shell
source code, in the form of vestigial, disabled support for a
'[
' builtin. In msg.c,
there is a commented out section toward the bottom:
[...] SYSTAB commands { {"cd", SYSCD}, {"read", SYSREAD}, /* {"[", SYSTST}, */ {"set", SYSSET}, [...]
Then in xec.c
there's commented out code that would have handled SYSTST in the
execute()
function:
[...] case SYSREAD: exitval=readvar(&com[1]); break; /* case SYSTST: exitval=testcmd(com); break; */ [...]
There's no actual 'testcmd()
' function in the V7 Bourne shell source
code, but we can guess what it might have done.
Given this disabled code and that the V7 'test
' itself supported
being used as '[
', it seems possible that this syntax was Bourne's
preference. It's possible that the builtin '[' was implemented and
then removed in favor of '[' being a hardlink to 'test', and then
for whatever reason other people in Bell Labs didn't use it and V7
wasn't distributed with such a hardlink set up (although individual
installs could make it themselves and it appears that the result
would work). However, this may have been the other way around, per
this HN comment,
with Bourne preferring the 'test' form over the '[' form.
As it happens, I don't think the 'test
' command (and its syntax)
appeared from nowhere in V7; instead I believe we can trace it to
antecedents in V6 Unix. But that's going to take another entry to
discuss, since this one is already long enough.