123

The following code exits with a unbound variable error. How to fix this, while still using the set -o nounset option?

#!/bin/bash

set -o nounset

if [ ! -z ${WHATEVER} ];
 then echo "yo"
fi

echo "whatever"
Asclepius
  • 49,954
  • 14
  • 144
  • 128
vinodkone
  • 2,481
  • 4
  • 19
  • 21

6 Answers6

123
#!/bin/bash

set -o nounset


VALUE=${WHATEVER:-}

if [ ! -z ${VALUE} ];
 then echo "yo"
fi

echo "whatever"

In this case, VALUE ends up being an empty string if WHATEVER is not set. We're using the {parameter:-word} expansion, which you can look up in man bash under "Parameter Expansion".

Flimm
  • 115,689
  • 38
  • 227
  • 240
Angelom
  • 2,283
  • 1
  • 14
  • 7
  • 20
    just replace if [ ! -z ${VALUE} ]; with if [ ! -z ${WHATEVER:-} ]; – Angelom Oct 20 '11 at 07:00
  • 20
    `:-` checks whether the variable is unset *or* empty. If you want to check *only* whether it's unset, use `-`: `VALUE=${WHATEVER-}`. Also, a more readable way to check whether a variable is empty: `if [ "${WHATEVER+defined}" = defined ]; then echo defined; else echo undefined; fi` – l0b0 Oct 24 '11 at 11:04
  • 1
    Also, this won't work if `$WHATEVER` contains only whitespace - See my answer. – l0b0 Mar 22 '12 at 15:07
  • 14
    Is there a reason for using "`! -z`" instead of just "`-n`" ? – Jonathan Hartley Aug 08 '19 at 20:55
36

You need to quote the variables if you want to get the result you expect:

check() {
    if [ -n "${WHATEVER-}" ]
    then
        echo 'not empty'
    elif [ "${WHATEVER+defined}" = defined ]
    then
        echo 'empty but defined'
    else
        echo 'unset'
    fi
}

Test:

$ unset WHATEVER
$ check
unset
$ WHATEVER=
$ check
empty but defined
$ WHATEVER='   '
$ check
not empty
l0b0
  • 52,149
  • 24
  • 132
  • 195
  • I tried this and I'm surprised this works... Everything is correct except according to **`"info bash"`**, **`"${WHATEVER-}"`** should have a `":"` (colon) before the `"-"` (dash) like: **`"${WHATEVER:-}"`**, and **`"${WHATEVER+defined}"`** should have a colon before the `"+"` (plus) like: **`"${WHATEVER:+defined}"`**. For me, it works either way, with or without the colon. On some versions of 'nix it probably won't work without including the colon, so it should probably be added. – Kevin Fegan Jan 07 '14 at 20:58
  • 10
    Nope, `-`, `+`, `:+`, and `:-` are all supported. The former detect whether the variable is *set*, and the latter detect whether it is *set **or** empty*. From `man bash`: "Omitting the colon results in a test only for a parameter that is unset." – l0b0 Jan 07 '14 at 21:00
  • 1
    Nevermind =). You are correct. I don't know how I missed that. – Kevin Fegan Jan 07 '14 at 21:11
  • From the [docs](https://www.gnu.org/software/bash/manual/bash.html#Shell-Parameter-Expansion): _Put another way, if the colon is included, the operator tests for both parameter’s existence and that its value is not null; if the colon is omitted, the operator tests only for existence._ – Asclepius Apr 03 '14 at 00:25
  • An explanation would be in order. E.g., what is the idea/gist? Why does it work with quoting? From [the Help Center](https://stackoverflow.com/help/promotion): *"...always explain why the solution you're presenting is appropriate and how it works"*. Please respond by [editing your answer](https://stackoverflow.com/posts/9824943/edit), not here in comments (***without*** "Edit:", "Update:", or similar - the answer should appear as if it was written today). – Peter Mortensen Mar 01 '22 at 16:50
12

How about a oneliner?

[ -z "${VAR:-}" ] && echo "VAR is not set or is empty" || echo "VAR is set to $VAR"

-z checks both for empty or unset variable

NublaII
  • 169
  • 1
  • 5
  • 2
    No, `-z` only checks if the next parameter is empty. `-z` is is just an argument of the `[` command. Variable expansion happens before `[ -z` can do anything. – dolmen Mar 30 '15 at 21:39
  • 1
    This seems like the correct solution, in that it does not generate an error if $VAR is not set. @dolmen can you provide an example of when it would not work? – Chris Stryczynski Nov 22 '17 at 14:29
  • @dolmen having read various bash resources about param expansion and finding the other answers over-complicated , i see nothing wrong with this one. so your “clarification”, while technically correct, seems rather pointless in practice, unless you need to differentiate unset vs empty. I tested unset, empty and non-empty, (bash 4) and it pretty much did what’s advertised each time. – JL Peyret Oct 11 '19 at 01:40
12

Assumptions:

$ echo $SHELL
/bin/bash
$ /bin/bash --version | head -1
GNU bash, version 4.1.2(1)-release (x86_64-redhat-linux-gnu)
$ set -o nounset

If you want a non-interactive script to print an error and exit if a variable is null or not set:

$ [[ "${HOME:?}" ]]

$ [[ "${IAMUNBOUND:?}" ]]
bash: IAMUNBOUND: parameter null or not set

$ IAMNULL=""
$ [[ "${IAMNULL:?}" ]]
bash: IAMNULL: parameter null or not set

If you don't want the script to exit:

$ [[ "${HOME:-}" ]] || echo "Parameter null or not set."

$ [[ "${IAMUNBOUND:-}" ]] || echo "Parameter null or not set."
Parameter null or not set.

$ IAMNULL=""
$ [[ "${IAMUNNULL:-}" ]] || echo "Parameter null or not set."
Parameter null or not set.

You can even use [ and ] instead of [[ and ]] above, but the latter is preferable in Bash.

Note what the colon does above. From the docs:

Put another way, if the colon is included, the operator tests for both parameter’s existence and that its value is not null; if the colon is omitted, the operator tests only for existence.

There is apparently no need for -n or -z.

In summary, I may typically just use [[ "${VAR:?}" ]]. Per the examples, this prints an error and exits if a variable is null or not set.

Asclepius
  • 49,954
  • 14
  • 144
  • 128
5

You can use

if [[ ${WHATEVER:+$WHATEVER} ]]; then

but

if [[ "${WHATEVER:+isset}" == "isset" ]]; then

might be more readable.

Aleš Friedl
  • 51
  • 1
  • 2
  • String comparisons should use the standard (POSIX) `=` operator, not `==` to aid in portability, and `[` instead of `[[` if possible. – Jens Oct 26 '12 at 09:30
  • 3
    @Jens The question is specific to bash and includes `set -o nounset` which is specific to bash. If you put a `#!/bin/bash` at the top of your script, it is actually best to use bash's enhancements. – Bruno Bronosky Jan 25 '17 at 14:49
1

While this isn't exactly the use case asked for above, I've found that if you want to use nounset (or -u) the default behavior is the one you want: to exit nonzero with a descriptive message.

It took me long enough to realize this that I figured it was worth posting as a solution.

If all you want is to echo something else when exiting, or do some cleanup, you can use a trap.

The :- operator is probably what you want otherwise.

Max Bileschi
  • 1,867
  • 2
  • 18
  • 18