Why does this bash conditional check work with [[ -n .. ]] but not [ -n .. ]?
this is because [[
takes an expression, and [
takes arguments which it translates into an expression.
[[
is syntax - it isn't a builtin command as [
is, but rather [[
is a compound command and is more akin to {
or (
than it is to [
.
in any case, because [[
is parsed alongside $expansions
, it has no difficulty understanding the difference between a null-valued expansion and a missing operand.
[
, however, is a routine run after all command-line expansions have already taken place, and by the time it evaluates its expressions, $null_expansion
has already been expanded away into nothing, and so all it receives is [ -n ]
, which may not be a valid expression. [
is spec'd to return true for a not-null single-argument case - as it does for -n
here - but the very same spec goes on to say...
The two commands:
test "$1" test ! "$1"
could not be used reliably on some historical systems. Unexpected results would occur if such a string expression were used and
$1
expanded to!
,(
, or a known unary primary (such as-n
). Better constructs are:test -n "$1" test -z "$1"
there are upsides and downsides to both forms. [
expressions can be constructed out of expansions, and so:
[ "-${z:-n}" "$var" ]
...could be a perfectly valid way to build a test, but is not doable with:
[[ "-${z:-n}" "$var" ]]
...which is a nonsense command. The differences are entirely due to the command-line parse-point at which the test is run.
Variables don't have to be double-quoted in [[ ]]
but they should be quoted for [ ]
.
Without quotes, if $value
is an empty string, [ -n $value ]
is exactly the same as [ "-n" ]
. "-n"
is a string test and evaluates as not-empty and thus true, so the test succeeds.
(why? because -n STRING
and STRING
are the same for [
aka test
- they're both a test for string-is-not-empty)
Further experiments indicate that this seems to be the case for all single-operand tests, including file operators - if there is no operand, [ ]
treats the single argument as a string test. This would probably be considered a bug and fixed if it wasn't a historical fact that script writers have come to depend upon over the decades - and changing it would break an uncountable number of existing scripts.
With quotes, [ -n "$value" ]
is exactly the same as [ -n "" ]
, so the test fails.
[[ ... ]]
is a lot more forgiving about quoting of variables - quoting is optional and it works the same with or without quotes.