Famously, '
[
' is a program, not a piece of shell syntax, and it's also known as '
test
' ( which was the original name for it ). On many systems, this was and is implemented by '
[
' being a hardlink to '
test
' (generally 'test' was the primary name for various reasons). However, today I found out that GNU Coreutils is an exception . Although the two names are built from the same source code ( src/test.c ), they're different binaries and the '
[
' binary is larger than the '
test
' binary. What is ultimately going on here is a piece of '
test
' behavior that I had forgotten about, that of the meaning of running '
test
' with a single argument.
The POSIX specification for test is straightforward. A single argument is taken as a string, and the behavior is the same as for -n, although POSIX phrases it differently:
- string
- True if the string string is not the null string; otherwise, false.
The problem for GNU Coreutils is that GNU programs like to support options like --help and --version. Support for these is specifically disallowed for '
test
', where '
test --help
' and '
test --version
' must both be silently true. However, this is not disallowed by POSIX for '
[
' if '
[
' is invoked without the closing '
]
':
$ [ --version [ (GNU coreutils) 9.1 [...] $ [ foo [: missing ‘]’ $ [ --version ] && echo true true
As we can see here, invoking 'test' as '
[
' without the closing '
]
' as an argument is an error, and GNU Coreutils is thus allowed to interpret the results of your error however it likes, including making '
[ --version
' and so on work.
(There's a comment about it in test.c .)
The binary size difference is presumably because the '
test
' binary omits the version and help text, along with the code to display it. But if you look at the relevant Coreutils test.c code , the relevant code isn't disabled with an #ifdef. Instead, LBRACKET is #defined to 0 when compiling the '
test
' binary. So it seems that modern C compilers are doing dead code elimination on the '
if
(LBRACKET) { ...}
' section, which is a well established optimization, and then going on to notice that the called functions like '
usage()
' are never invoked and dropping them from the binary. Possibly this is set with some special link time magic flags.
PS: This handling of a single argument for
test
goes all the way back to V7, where
test
was actually pretty smart . If I'm reading the V7 test(1) manual page correctly, this behavior was also documented.
PPS: In theory GNU Coreutils is portable and you might find it on any Unix. In practice I believe it's only really used on Linux.