7

If I want to perform some commands given variables aren't set I'm using:

if [[ -z "$a" || -z "$v" ]]
then
  echo "a or b are not set"
fi

Yet the same syntax doesn't work with -v, I have to use:

if [[ -v a && -v b ]]
then
  echo "a & b are set"
fi

What is the history behind this? I don't understand why the syntax wouldn't be the same. I've read that -v is a somewhat recent addition to bash (4.2)?

  • What's the question? Are you asking why you need to use the dollar sign? – choroba Oct 6 '17 at 15:02
  • a variable can be set to "" – Kevin Oct 6 '17 at 21:21
20

Test operators -v and -z are just not the same.

Operator -z tells if a string is empty. So it is true that [[ -z "$a" ]] will give a good approximation of "variable a is unset", but not a perfect one:

  • the expression will yield true if a is set to the empty string rather than unset;

  • the enclosing script will fail if a is unset and the option nounset is enabled.

On the other hand, -v a will be exactly "variable a is set", even in edge cases. It should be clear that passing $a rather than a to -v would not be right, as it would expand that possibly-unset variable before the test operator sees it; so it has to be part of that operator's task to inspect that variable, pointed to by its name, and tell whether it is set.

  • When you say edge case do you mean they handle empty differently? For example a=""? – Philip Kirkbride Oct 6 '17 at 15:07
  • Yes, that's exactly what I mean. – dhag Oct 6 '17 at 15:09
  • 3
    Note that there are edge cases for [[ -v ]] as well. Like for a variable array type, [[ -v var ]] returns true only if ${var[0]} (in effect same as $var) is set. For associative array, ${var[0]} as well (not ${var[00]} for instance). [[ -v var[@] ]] would return true if the array has any value set (would return false for a variable set as var=()). That's down to the crazy way variable typing is done in ksh/bash. See zsh for a less confusing variable typing. – Stéphane Chazelas Oct 6 '17 at 15:19
  • Ah, good point. You noticed how I carefully didn't get into the array issue :). – dhag Oct 6 '17 at 15:30
  • 2
    More succinctly: -z tests a value, while -a tests a name. – chepner Oct 6 '17 at 18:12
7

There's a difference in the meaning of -z and -v:

echo Empty:
x=""  # Same with x=
[[ -z $x ]] && echo z
[[ -v  x ]] && echo v
unset x

echo Unset
[[ -z $x ]] && echo z
[[ -v  x ]] && echo v

By using -z, you can't distinguish a variable that was assigned an empty value from a variable that hasn't been assigned any value.

Also, [[ -z $x ]] is still sensible to set -u, while [[ -v x ]] isn't.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.