21

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 Friske
  • 2,260
  • 1
    -v isn't an option to test, but an operator for conditional expressions. – Tim May 02 '16 at 06:33
  • 1
    @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. –  May 07 '16 at 01:10

3 Answers3

43

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.

  • 1
    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? – Tim Friske Nov 27 '12 at 09:29
  • @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. – Gilles 'SO- stop being evil' Nov 27 '12 at 10:56
  • 1
    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. – Tim Friske Nov 27 '12 at 21:57
  • 1
    @TimFriske I know that the bash implementation conforms to its documentation. But treating an empty array like an undefined variable is really strange design. – Gilles 'SO- stop being evil' Nov 27 '12 at 22:19
  • 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 ....) – will Apr 11 '16 at 14:48
  • @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). – Gilles 'SO- stop being evil' Apr 11 '16 at 15:16
7

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.
Tim Friske
  • 2,260
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
  • Had to remove [@] if array has more than one value. – Stein Inge Morisbak Apr 17 '13 at 09:22
  • 2
    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. – Ron Burk Jun 15 '15 at 02:00
  • 1
    only works if your scripts are running with undefined variables allowed (no set -u) – Florian Heigl Jan 22 '18 at 13:09