Why do [[ -z ]] and [[ -v ]] have different syntax?

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.

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.