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?

Tim
  • 101,790

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
  • 333,661
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
  • 72,889
ilkkachu
  • 138,973
  • 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),
    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.