Collectives™ on Stack Overflow

Find centralized, trusted content and collaborate around the technologies you use most.

Learn more about Collectives

Teams

Q&A for work

Connect and share knowledge within a single location that is structured and easy to search.

Learn more about Teams

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

#!/bin/bash
set -o nounset
if [ ! -z ${WHATEVER} ];
 then echo "yo"
echo "whatever"
                :- 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, 2011 at 11:04

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

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

Test:

$ unset WHATEVER
$ check
unset
$ WHATEVER=
$ check
empty but defined
$ WHATEVER='   '
$ check
not empty
                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 7, 2014 at 20:58
                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 7, 2014 at 21:00
                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.
– Asclepius
                Apr 3, 2014 at 0:25

Use 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

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, 2015 at 21:39 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, 2017 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, 2019 at 1:40 $ /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 documentation:

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.

String comparisons should use the standard (POSIX) = operator, not == to aid in portability, and [ instead of [[ if possible. – Jens Oct 26, 2012 at 9:30 @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, 2017 at 14:49

While this isn't exactly the use case asked for, 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.

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.

To me, most of the answers are at best confusing, not including a test matrix. They also often do not address the scenario where variable contains the defaulting value.

The solution from l0b0 is the only readable, testable (and correct in respect to the actual question IMO), but it is unclear if inverting/reordering the tests to simplify the logic produces correct result. I minified hirs solution

The (already minified) contrast solution from Aleš, exposes the difference of a variable being declared but undefined. The one or the other might fit your scenario.

#!/bin/bash -eu
check1() {
    if [[ -n "${WHATEVER-}" ]]; then
        echo 'something else: not empty'
    elif [[ "${WHATEVER+defined}" = defined ]]; then
        echo 'something else: declared but undefined'
        echo 'unset'
check2() {
    if [[ "${WHATEVER+defined}" != "defined" ]]; then
        echo 'unset'
        echo "something else"
check3() {
    if [[ "${WHATEVER-defined}" = defined ]]; then
        echo 'unset'
        echo 'something else'
check4() {
    if [[ ! ${WHATEVER+$WHATEVER} ]]; then
        echo 'unset'
        echo 'something else'
echo "check1 from l0b0"
unset WHATEVER
check1
WHATEVER=
check1
WHATEVER='   '
check1
WHATEVER='defined'
check1
echo "check2 prove simplification keeps semantics"
unset WHATEVER
check2
WHATEVER=
check2
WHATEVER='   '
check2
WHATEVER='defined'
check2
echo "check3 other promising operator?"
unset WHATEVER
check3
WHATEVER=
check3
WHATEVER='   '
check3
WHATEVER='defined'
check3
echo "check4 other interesting suggestion, from aleš"
unset WHATEVER
check4
WHATEVER=
check4
WHATEVER='   '
check4
WHATEVER='defined'
check4
  • Check1 and Check2 behave identically
  • Check3 is plainly wrong
  • Check4: correct, depending on what you consider a declared/defined variable.
  • check1 from l0b0
    unset
    something else: declared but undefined
    something else: not empty
    something else: not empty
    check2 prove simplification keeps semantics
    unset
    something else
    something else
    something else
    check3 other promising operator?
    unset
    something else
    something else
    unset
    check4 other interesting suggestion, from aleš
    unset
    unset
    something else
    something else
            

    Thanks for contributing an answer to Stack Overflow!

    • Please be sure to answer the question. Provide details and share your research!

    But avoid

    • Asking for help, clarification, or responding to other answers.
    • Making statements based on opinion; back them up with references or personal experience.

    To learn more, see our tips on writing great answers.