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,190
  • 3
  • 23
  • 36
  • 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

42

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.

Gilles 'SO- stop being evil'
  • 807,993
  • 194
  • 1,674
  • 2,175
  • 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?](http://stackoverflow.com/a/13864829/108350) --> **[The Right Way](http://stackoverflow.com/a/13864829/108350)** ... 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,190
  • 3
  • 23
  • 36
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