How to test if a variable is defined at all in Bash prior to version 4.2 with the nounset shell option?

53,277

Solution 1

Portable to all POSIX shells:

if [ -n "${foobar+1}" ]; then
  echo "foobar is defined"
else
  echo "foobar is not defined"
fi

Make that ${foobar:+1} if you want to treat foobar the same way whether it is empty or not defined. You can also use ${foobar-} to get an empty string when foobar is undefined and the value of foobar otherwise (or put any other default value after the -).

In ksh, if foobar is declared but not defined, as in typeset -a foobar, then ${foobar+1} expands to the empty string.

Zsh doesn't have variables that are declared but not set: typeset -a foobar creates an empty array.

In bash, arrays behave in a different and surprising way. ${a+1} only expands to 1 if a is a non-empty array, e.g.

typeset -a a; echo ${a+1}    # prints nothing
e=(); echo ${e+1}            # prints nothing!
f=(''); echo ${f+1}          # prints 1

The same principle applies to associative arrays: array variables are treated as defined if they have a non-empty set of indices.

A different, bash-specific way of testing whether a variable of any type has been defined is to check whether it's listed in ${!PREFIX*}. This reports empty arrays as defined, unlike ${foobar+1}, but reports declared-but-unassigned variables (unset foobar; typeset -a foobar) as undefined.

case " ${!foobar*} " in
  *" foobar "*) echo "foobar is defined";;
  *) echo "foobar is not defined";;
esac

This is equivalent to testing the return value of typeset -p foobar or declare -p foobar, except that typeset -p foobar fails on declared-but-unassigned variables.

In bash, like in ksh, set -o nounset; typeset -a foobar; echo $foobar triggers an error in the attempt to expand the undefined variable foobar. Unlike in ksh, set -o nounset; foobar=(); echo $foobar (or echo "${foobar[@]}") also triggers an error.

Note that in all situations described here, ${foobar+1} expands to the empty string if and only if $foobar would cause an error under set -o nounset.

Solution 2

To sum up with Gilles' answer I made up my following rules:

  1. Use [[ -v foobar ]] for variables in Bash version >= 4.2.
  2. Use declare -p foobar &>/dev/null for array variables in Bash version < 4.2.
  3. Use (( ${foo[0]+1} )) or (( ${bar[foo]+1} )) for subscripts of indexed (-a) and keyed (-A) arrays (declare), respectively. Options 1 and 2 don't work here.

Solution 3

I use the same technique for all variables in bash, and it works, e.g.:

[ ${foobar} ] && echo "foobar is set" || echo "foobar is unset"

outputs:

foobar is unset

whilst

foobar=( "val" "val2" )
[ ${foobar} ] && echo "foobar is set" || echo "foobar is unset"

outputs:

foobar is set
Share:
53,277

Related videos on Youtube

Tim Friske
Author by

Tim Friske

Updated on September 18, 2022

Comments

  • Tim Friske
    Tim Friske almost 2 years

    For Bash versions prior to "GNU bash, Version 4.2" are there any equivalent alternatives for the -v option of the test command? For example:

    shopt -os nounset
    test -v foobar && echo foo || echo bar
    # Output: bar
    foobar=
    test -v foobar && echo foo || echo bar
    # Output: foo
    
    • Tim
      Tim about 8 years
      -v isn't an option to test, but an operator for conditional expressions.
    • Admin
      Admin about 8 years
      @Tim It is three things, beside being a token, an string and part of a line: An option to a command test -v, an operator to a conditional expression and a unary test primary for [ ]. Don't mix English language with shell definitions.
  • Tim Friske
    Tim Friske over 11 years
    What about arrays? In Bash version "GNU bash, version 4.1.10(4)-release (i686-pc-cygwin)" echo "${foobar:+1}" doesn't print 1 if declare -a foobar was previously issued and thus foobar is an indexed array. declare -p foobar correctly reports declare -a foobar='()'. Does "${foobar:+1}" only work for non-array variables?
  • Gilles 'SO- stop being evil'
    Gilles 'SO- stop being evil' over 11 years
    @TimFriske ${foobar+1} (without the :, I inverted two examples in my original answer) is correct for arrays in bash if your definition of “defined” is “would $foobar work under set -o nounset”. If your definition is different, bash is a bit weird. See my updated answer.
  • Tim Friske
    Tim Friske over 11 years
    Regarding the topic "In bash, arrays behave in a different and surprising way." the behavior can be explained from the bash(1) manpages, section "Arrays". It states that "Referencing an array variable without a subscript is equivalent to referencing the array with a subscript of 0.". Thus if neither a 0 index nor a key is defined as it is true for a=(), ${a+1} correctly returns nothing.
  • Gilles 'SO- stop being evil'
    Gilles 'SO- stop being evil' over 11 years
    @TimFriske I know that the bash implementation conforms to its documentation. But treating an empty array like an undefined variable is really strange design.
  • rj487
    rj487 about 11 years
    Had to remove [@] if array has more than one value.
  • Ron Burk
    Ron Burk about 9 years
    This works great, so long as you want to test whether it has a value, not whether it's been defined. I.e., foobar="" will then report that foobar is unset. No wait, I take that back. Really only tests if first element is empty or not, so it seems to be only a good idea if you know the variable is NOT an array, and you only care about emptiness, not definedness.
  • will
    will about 8 years
    I prefer the result from: How to check if a variable is set in Bash? --> The Right Way ... I strikes a chord with how I thing this ought to work. Dare I say, Windows has a defined operator. Test could DO that; it can't be difficult (um ....)
  • Gilles 'SO- stop being evil'
    Gilles 'SO- stop being evil' about 8 years
    @will My first command is the same as the “Right Way”, we just inverted the test (if set vs if unset). The rest of my answer is a digression on bash arrays. The test command doesn't do that because it was historically implemented as a separate command (and it still is, but most if not all modern and even not-so-modern shells also have it built in).
  • Florian Heigl
    Florian Heigl over 6 years
    only works if your scripts are running with undefined variables allowed (no set -u)