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?

Gilles 'SO- stop being evil'
  • 807,993
  • 194
  • 1,674
  • 2,175
ivan
  • 1,858
  • 2
  • 19
  • 37

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
  • 133,243
  • 15
  • 236
  • 397
  • 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](https://github.com/ivanbrennan/ctags-config/commit/3658c6eb5f23a47a2286dd8f6ea80fd81947af31) 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.

Greg A. Woods
  • 793
  • 5
  • 9
  • Is `${VAR-}` really deprecated? It's behavior differs from the `:-` version if `VAR` is set to null. – ivan Jun 15 '17 at 04:10
  • 2
    `${VAR:-foo}` and `${VAR-foo}` are not the same, and there's no reason to think one is deprecated. (Okay, fine, with an empty default value, there's no difference.) – ilkkachu Jun 15 '17 at 04:11
  • Well, there's been no mention of `${VAR-}` in POSIX since at least 1003.1-2004. It's not in the 10th Edition Research Unix manual either, and that came out in 1989. – Greg A. Woods Jun 15 '17 at 04:17
  • 2
    @GregA.Woods, [IEEE Std 1003.1-2008, 2016 Edition, Shell Command Language/Parameter Expansion](http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_06_02). There's a nice table of the eight similar forms. The manuals for some shells are a bit more vague on it, e.g. [Bash's manual](https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html) only mentions in the middle of the text what happens if the colon is omitted. – ilkkachu Jun 15 '17 at 04:37
  • Ah, my mistake -- my reference to the 2004 edition was incomplete. (I wish POSIX hadn't used the word "null" to refer to an *empty* shell variable. To most programmers with experience with pointers (or SQL for that matter) "null" means un-set, and "empty" is, well, empty.) – Greg A. Woods Jun 15 '17 at 05:10
  • It's not the best choice of word, I agree. But luckily they do at least [define the null string](http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_250), so there's that. (Even though it's just a referral to the empty string, eh.) – ilkkachu Jun 16 '17 at 08:21