5

I see a lot of shell scripts (for example, this one) checking for a variable's presence/absence like:

[ -n "${VAR-}" ]

As far as I can tell, using the ${VAR-fallback} form without providing a fallback serves no purpose when checking for variable presence/absence (-n or -z). The same goes for ${VAR:-fallback}. For example, with an unset variable,

unset VAR
[ -z "$VAR" ]       &&
  [ -z "${VAR-}" ]  &&
  [ -z "${VAR:-}" ] &&
  echo True        # => True

and with a null variable

VAR=
[ -z "$VAR" ]       &&
  [ -z "${VAR-}" ]  &&
  [ -z "${VAR:-}" ] &&
  echo True        # => True

But I see it in enough places that I have to ask, am I missing something? Is it just a misunderstanding that results in misuse, or is there actually a reason to do it?

ivan
  • 1,878

2 Answers2

7

If set -u is in effect, and VAR is unset, [ -z "$VAR" ] will cause an error. With [ -z "${VAR-}" ] the default value overrides the check for using an unset variable, and there is no error.

ilkkachu
  • 138,973
  • Well, set -u is very unusual in practice, and normally would only be seen inside the script itself, which again would be highly unusual. It can be useful for debugging I suppose, but defending against it in normal scripting is pedantic, to say the least. – Greg A. Woods Jun 15 '17 at 04:23
  • BTW, use of set -u in conjunction with pervasive use of ${VAR:-} would be self-defeating to the extreme. – Greg A. Woods Jun 15 '17 at 04:27
  • @GregA.Woods, regardless of how usual it is, if it's enabled, there is a difference. I do rather find set -u useful. Not as a way to crash on purpose, but as a way to hedge against mistyping variable names. Even if you used "${foo-}" everywhere, it would have the advantage of explicitly making you think about the possibility of the variable being unset. – ilkkachu Jun 15 '17 at 04:28
  • I'm not arguing against the use of set -u -- just against the insanity of defending against it blindly with ${VAR:-}. – Greg A. Woods Jun 15 '17 at 04:35
  • @GregA.Woods There are cases, however, where it's not done blindly. I just updated a script of my own to use this combination intentionally. Here's an example from a script I wrote, which I've just updated in light of this discussion. The top of that script does set -eu, which is easy to miss in the diff I linked. – ivan Jun 15 '17 at 12:32
  • @ivan, btw, set -e doesn't error out if a command substitution fails, so you don't need that || : within it. – ilkkachu Jun 15 '17 at 12:48
  • 1
    ${var-} is useful when checking if a variable that comes from outside the script itself is set or empty (or ${var+x} to check if it's set). I've used stuff like set -u; if [ -z "${1-}" ] ; then complain... (Yes, you could use $# for positional parameters, but not for some environment variable in general.) – ilkkachu Jun 15 '17 at 12:51
  • @ilkkachu I wasn't sure about the set -e and the command substitution, so I tried it out, and it did actually error out without the || :. Maybe it depends on the bash version? – ivan Jun 15 '17 at 16:13
  • I've almost always found it infinitely more robust to exit the script with a proper exit command, and usually also preceded by an explicit and informative error message. Having the shell bomb out itself isn't very useable except in the most trivial cases such as one-off scripts, or script fragments used within makefiles, etc. That means doing tests for unset or empty variables in ways that don't cause the shell to die. – Greg A. Woods Jun 15 '17 at 17:17
  • 2
    I also find it a lot more robust to avoid relying on any distinction between empty and unset variables, especially in shell scripts. Binary logic is usually much easier to follow and prove correct than ternary logic. – Greg A. Woods Jun 15 '17 at 17:18
  • @GregA.Woods I'm enjoying the nuances of parameter expansion as I get more familiar with it, but yeah, it's best to keep things as simple as possible, though not simpler (to paraphrase a great man). – ivan Jun 16 '17 at 00:55
  • @GregA.Woods, my main point on set -u was to hedge against mistyping variable names. If you don't make mistakes, you won't need it. But if you use it, you have some situations where you may also need the default value expansions. – ilkkachu Jun 16 '17 at 08:16
  • Still, set -u is more of a sledge hammer than a fine programming tool. For a one-off script it might be OK, but for anything big enough where misspellings are hard to find, it's going to be more trouble than it's worth, even in the test and debug phase. I'm more inclined to use the better tools for the job. Modern text editors are pretty good at finding mis-spelled variables, though with some it can be a bit more tedious than with others. – Greg A. Woods Jun 16 '17 at 19:59
-2

You are absolutely right -- there is no reason to use the form ${VAR:-} nor the form ${VAR-} with either test -n or test -z.

I've not seen this myself, so I can only guess that inexperienced shell programmers might think there's some difference between an unset value and an empty value with respect to how test -n works, but that could only happen if test (i.e. [) is both a built-in, and buggy; or if someone is trying to use set -u without thinking through the implications of then defending against it in this manner.