In Bash, how do I test if a variable is defined in "-u" mode
BashBash 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.
}
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.)