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.

Tags:

Bash

Test