In Bash, how do I test if a variable is defined in "-u" mode

Bash

Bash Problem Overview


I just discovered set -u in bash and it helped me find several previously unseen bugs. But I also have a scenario where I need to test if a variable is defined before computing some default value. The best I have come up with for this is:

if [ "${variable-undefined}" == undefined ]; then
    variable="$(...)"
fi

which works (as long as the variable doesn't have the string value undefined). I was wondering if there was a better way?

Bash Solutions


Solution 1 - Bash

This is what I've found works best for me, taking inspiration from the other answers:

if [ -z "${varname-}" ]; then
  ...
  varname=$(...)
fi

Solution 2 - Bash

What Doesn't Work: Test for Zero-Length Strings

You can test for undefined strings in a few ways. Using the standard test conditional looks like this:

# Test for zero-length string.
[ -z "$variable" ] || variable='foo'

This will not work with set -u, however.

What Works: Conditional Assignment

Alternatively, you can use conditional assignment, which is a more Bash-like way to do this. For example:

# Assign value if variable is unset or null.
: "${variable:=foo}"

Because of the way Bash handles expansion of this expression, you can safely use this with set -u without getting a "bash: variable: unbound variable" error.

Solution 3 - Bash

In bash 4.2 and newer there is an explicit way to check whether a variable is set, which is to use -v. The example from the question could then be implemented like this:

if [[ ! -v variable ]]; then
   variable="$(...)"
fi

See http://www.gnu.org/software/bash/manual/bashref.html#Bash-Conditional-Expressions

If you only want to set the variable, if it is not already set you are probably better of doing something along these lines:

variable="${variable-$(...)}"

Note that this does not deal with a defined but empty variable.

Solution 4 - Bash

The answers above are not dynamic, e.g., how to test is variable with name "dummy" is defined? Try this:

is_var_defined()
{
    if [ $# -ne 1 ]
    then
        echo "Expected exactly one argument: variable name as string, e.g., 'my_var'"
        exit 1
    fi
    # Tricky.  Since Bash option 'set -u' may be enabled, we cannot directly test if a variable
    # is defined with this construct: [ ! -z "$var" ].  Instead, we must use default value
    # substitution with this construct: [ ! -z "${var:-}" ].  Normally, a default value follows the
    # operator ':-', but here we leave it blank for empty (null) string.  Finally, we need to
    # substitute the text from $1 as 'var'.  This is not allowed directly in Bash with this
    # construct: [ ! -z "${$1:-}" ].  We need to use indirection with eval operator.
    # Example: $1="var"
    # Expansion for eval operator: "[ ! -z \${$1:-} ]" -> "[ ! -z \${var:-} ]"
    # Code  execute: [ ! -z ${var:-} ]
    eval "[ ! -z \${$1:-} ]"
    return $?  # Pedantic.
}

Related: https://stackoverflow.com/q/3601515/257299

Solution 5 - Bash

Unfortunatly [[ -v variable ]] is not supported in older versions of bash (at least not in version 4.1.5 I have on Debian Squeeze)

You could instead use a sub shell as in this :

if (true $variable)&>/dev/null; then
    variable="$(...)"
fi

Solution 6 - Bash

In the beginning of your script, you could define your variables with an empty value

variable_undefined=""

Then

if [ "${variable_undefined}" == "" ]; then
    variable="$(...)"
fi

Solution 7 - Bash

if [ "${var+SET}" = "SET" ] ; then
    echo "\$var = ${var}"
fi

I don't know how far back ${var+value} is supported, but it works at least as far back as 4.1.2. Older versions didn't have ${var+value}, they only had ${var:+value}. The difference is that ${var:+value} will only evaluate to "value" if $var is set to a nonempty string, while ${var+value} will also evaluate to "value" if $var is set to the empty string.

Without [[ -v var ]] or ${var+value} I think you'd have to use another method. Probably a subshell test as was described in a previous answer:

if ( set -u; echo "$var" ) &> /dev/null; then
    echo "\$var = ${var}
fi

If your shell process has "set -u" active already it'll be active in the subshell as well without the need for "set -u" again, but including it in the subshell command allows the solution to also work if the parent process hasn't got "set -u" enabled.

(You could also use another process like "printenv" or "env" to test for the presence of the variable, but then it'd only work if the variable is exported.)

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionRamonView Question on Stackoverflow
Solution 1 - BashRamonView Answer on Stackoverflow
Solution 2 - BashTodd A. JacobsView Answer on Stackoverflow
Solution 3 - BashFelix LeipoldView Answer on Stackoverflow
Solution 4 - BashkevinarpeView Answer on Stackoverflow
Solution 5 - BashefaView Answer on Stackoverflow
Solution 6 - BashLuc MView Answer on Stackoverflow
Solution 7 - BashGeorgeView Answer on Stackoverflow