9

I have a script that fails to detect zero length strings,the script uses [ -n $value ] in a bash conditional expression, i.e.

#!/usr/bin/env bash

value=""
if [ -n $value ]
then
    echo "value is non-zero"
fi

result

value is non-zero

If I use [[ -n $value ]] it works, i.e.

#!/usr/bin/env bash

value=""
if [[ -n $value ]]
then
    echo "value is non-zero"
fi

using [[ produces no output as expected. From the man page:

   [[ expression ]]
      Return a status of 0 or 1 depending on the evaluation of the conditional expression expression.  Expressions are composed of the pri‐
      maries described below under CONDITIONAL EXPRESSIONS.  Word splitting and pathname expansion are not performed on the  words  between
      the  [[  and ]]; tilde expansion, parameter and variable expansion, arithmetic expansion, command substitution, process substitution,
      and quote removal are performed.  Conditional operators such as -f must be unquoted to be recognized as primaries.

I can't make out an explanation for the behaviour from this.
Why does [[ detect zero length strings but [ doesn't?

marked as duplicate by Gilles bash Nov 30 '15 at 22:19

This question has been asked before and already has an answer. If those answers do not fully address your question, please ask a new question.

17

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.

  • 1
    @user4668401 [[ is not more user-friendly, it is more innate. [ and test were, historically, separate executable commands found in $PATH, and so shell [ builtins were designed to behave as an outside command would. [[ has never had that distinction, however, and so it has no need to behave as an outside executable might, and it can address names and information the shell possesses as if it is its own (because it is). – mikeserv Nov 30 '15 at 4:55
  • 2
    ah right! NOW I see . so the reason for all the quoting and expansion issues is because bash is literally outsourcing the task to another program – the_velour_fog Nov 30 '15 at 5:02
  • 2
    almost right. [ and test ARE external commands, but many shells also have them as built-ins for convenience and speed - but the built-in versions are bug-for-bug compatible so they don't break existing scripts. – cas Nov 30 '15 at 5:05
  • 1
    @user4668401 - not exactly. [ is a bash builtin as well, but it is a more separate routine than is [[. – mikeserv Nov 30 '15 at 5:05
  • 4
    right, but regardless of which version of [ is installed and used by bash - whenever you use [ you need to have the mindset of I'm calling another program so I need to handle parameter passing accordingly – the_velour_fog Nov 30 '15 at 5:12
9

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.

  • Ah ok, so if [ -n $value ] is used - bash evaluates $value before it performs it passes it to the [ command, so the parameter [ sees is "" ? – the_velour_fog Nov 30 '15 at 4:46
  • ah, and hence syntax error - because no closing ] – the_velour_fog Nov 30 '15 at 4:48
  • [[ does not work the same with or without quotes. – mikeserv Nov 30 '15 at 5:09
  • it does in this context. – cas Nov 30 '15 at 5:11
  • not even in this context. try quoting -n. – mikeserv Nov 30 '15 at 5:16

Not the answer you're looking for? Browse other questions tagged or ask your own question.