2

I want to test if a parameter is an empty string "".

When the parameter is not set, the test should fail.

  1. Why does the following not succeed?

    $ unset aa
    $ if [ ${aa}=="" ]; then echo yes; else echo no; fi
    yes
    
  2. What shall I do instead?

Vlastimil Burián
  • 27,586
  • 56
  • 179
  • 309
Tim
  • 98,580
  • 191
  • 570
  • 977

3 Answers3

6

Your (attempted) test $aa == "" is equivalent of comparing two empty strings with each other, which results in a true result. This is because the shell will expand unset variables to the empty string. Without the spaces around the ==, the test will always be true as it's the same as testing on the two character string ==. This string is always true.

Instead, in bash:

$ unset aa
$ if [ -v aa ]; then echo Set; else echo Not set; fi
Not set
$ aa=""
$ if [ -v aa ]; then echo Set; else echo Not set; fi
Set

The full test for an empty string would therefore be

if [ -v aa ] && [ -z "$aa" ]; then
    echo Set but empty
fi

From help test in bash:

-v VAR True if the shell variable VAR is set.

Or, from the bash manual's "CONDITIONAL EXPRESSIONS" section:

-v varname

True if the shell variable varname is set (has been assigned a value).

With the -v test, you test on the variable's name rather than on its value.


If you really want to do a string comparisson, you could do something like

if [[ "${aa-InvalidValue}" != "InvalidValue" ]] && [ -z "$aa" ]; then
    echo Set but empty
fi

The expansion ${variable-value} will expand to value if variable is unset.

Note that the very similar ${variable:-value} will expand to value if variable is unset or null (the empty string).

Kusalananda
  • 320,670
  • 36
  • 633
  • 936
4

test, i.e. [, needs whitespace around the operators, basically because otherwise foo== would be taken as containing an operator, even though it's a valid string. The [[ .. ]] construct works the same in this. (See this question and BashFAQ 031 for the [ vs [[ difference.)

So, [ ${aa}=="" ] will always be true, since [ string ] is the same as [ -n string ], i.e. it tests that the string is not empty. And here the string contains at least ==.

Then [ ${aa} == "" ] will be an error if aa is unset or empty, since an empty variable expanded without quotes disappears and [ == foo ] isn't a valid test. If aa has a nonempty value, it's false. ([[ ${aa} == "" ]] would work even without the quotes, since [[ .. ]] is special.)

Of course [ "${aa}" == "" ] would test that aa is either unset or empty, the same as [ -z "${aa}" ].


To test that it's both set, and empty, we could use [ "${aa+x}" ] && [ -z "$aa" ], or a nifty combination of those, stolen from an answer by @Stéphane Chazelas:

if [ "${aa+x$aa}" = "x" ] ; then
    echo "aa is empty but set"
fi

(that works since if aa is unset, the + expansion expands to the empty string, and if it's set, it expands to x$aa, which is just x if aa is empty.)

$ foo() { [ "${1+x$1}" = "x" ] && echo "set but empty" || echo "unset or non-empty"; }    
$ foo; foo ""; foo bar
unset or non-empty
set but empty
unset or non-empty
muru
  • 69,900
  • 13
  • 192
  • 292
ilkkachu
  • 133,243
  • 15
  • 236
  • 397
  • 1
    Thanks. Is "[[ ${aa} == "" ]] would work even without the quotes, since [[ .. ]] is special" mentioned in bash manual? – Tim Jan 27 '18 at 22:36
  • @Tim, yeah, it's special in that it's not a regular command like `[`, but part of the shell syntax. The manual at least mentions that word splitting doesn't happen in it, and it's listed along with other shell constructs, like `if`, and `(( .. ))` ([here](https://www.gnu.org/software/bash/manual/html_node/Conditional-Constructs.html#index-_005b_005b)), See also: https://unix.stackexchange.com/a/32227/170373 – ilkkachu Jan 27 '18 at 22:41
  • 1
    To be pedantic, you could say that `test` i.e. `[` requires that operators and strings must be *separate arguments,* and that *in the shell* this is accomplished by putting whitespace around the operators. Since strictly speaking `test` never sees the whitespace itself (the shell strips it), and if anyone is perverse enough to invoke `test` from C code with an `execve` call, they won't need whitespace. – Wildcard Jan 28 '18 at 08:11
3

First, And I assume you do know, the == require spaces around it. The use of == is valid only in bash, ksh and zsh, better use =. Also, the variables expanded inside a test should be quoted. So, I'll assume that the line is actually:

unset aa ; if [ "${aa}" = "" ]; then echo yes; else echo no; fi

With that, your questions:

Why does the following not succeed?

Because the expansion of a plain unset var is identical to the expansion of a plain variable set to null (from what test '[' could do).

I mean, "$aa" is the same value for both an unset aa or a null aa.

To show how that works, try this script (note that the script is sh, it works the same in dash, bash, ksh and/or zsh):

#!/bin/sh

blanktest(){
    if [ "${aa}" = "" ]; then echo "$1 yes"; else echo "$1 no"; fi
}

unset aa; blanktest unset
unset aa; aa="";  blanktest blank
unset aa; aa="e"; blanktest value

Which, on execution will yield:

$ ./script
unset yes
blank yes
value no

What shall I do instead?

Use a parameter expansion called "Use Alternative Value." :

${aa+x}

which will yield an x if the variable is set (either null or value) and a null if the variable is unset.

Use this test instead:

[ "x${aa}x" = "x${aa+x}" ] && echo yes || echo no

Testing script (again, sh compatible):

#!/bin/sh

blanktest(){
    if [ "x${aa}x" = "x${aa+x}" ]; then echo "$1 yes"; else echo "$1 no"; fi
}

unset aa; blanktest unset
unset aa; aa="";  blanktest blank
unset aa; aa="e"; blanktest value

On execution, will print this:

$ ./script
unset no
blank yes
value no

There are several possible variations, if you are interested.