68

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?

Ramon
  • 8,202
  • 4
  • 33
  • 41
  • 1
    possible duplicate of [How to test if a variable is set in bash when using "set -o nounset"](http://stackoverflow.com/questions/7832080/how-to-test-if-a-variable-is-set-in-bash-when-using-set-o-nounset) – Gili Nov 15 '13 at 15:01

7 Answers7

70

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

if [ -z "${varname-}" ]; then
  ...
  varname=$(...)
fi
Ramon
  • 8,202
  • 4
  • 33
  • 41
  • 5
    Can someone explain why `"${varname-}"` **and** `"${varname:-}"` works? I am surprised the first works. – kevinarpe Mar 19 '14 at 07:22
  • 5
    http://www.tldp.org/LDP/abs/html/parameter-substitution.html ${varname-} is a form of ${varname-default}. If varname is not set the default is returned, in this case an empty string. – Aaron J Lang Apr 29 '14 at 10:16
  • 11
    The colon-free versions are described in a quick sentence in the man page just before the various operators are described. It's easy to miss. `${varname:-foo}` expands to `foo` if `varname` is unset *or* set to the empty string; `${varname-foo}` only expands to `foo` if `varname` is unset. – chepner Nov 06 '14 at 14:15
  • 1
    This still can't distinguish a variable that is set to empty value vs. a variable that is undefined. – haridsv Jun 30 '17 at 05:49
36

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.

Todd A. Jacobs
  • 81,402
  • 15
  • 141
  • 199
  • 3
    This doesn't quite answer the question as it doesn't provide a way to test for an undefined variable, simply a way to set it if it is. If other logic is desired, this won't help. – Andrew Ferrier Mar 20 '14 at 13:20
  • This answer appears to be more suitable for simply doing a test: http://unix.stackexchange.com/a/56846/18985 – Andrew Ferrier Mar 20 '14 at 13:50
  • 3
    @AndrewFerrier This question specifically refers to assigning a default value if the variable is unset; while there are valid use cases for testing if a variable is set without subsequently setting it, this question does not require it. – chepner Nov 06 '14 at 14:20
  • 4
    what is that leading `:` doing ? – PypeBros Dec 21 '17 at 11:53
  • @PypeBros apparently `:` is aliased to true: https://stackoverflow.com/questions/3224878/what-is-the-purpose-of-the-colon-gnu-bash-builtin – Bryce Guinta Dec 17 '18 at 18:54
  • so ... `: $COMMAND` would evaluate to true whatever the outcome of the command ? a bit like `- COMMAND` with makefiles ? – PypeBros Dec 20 '18 at 12:59
  • Pragmatically, the leading `:` provides the shell a way to perform expansions that set values without having to use more verbose constructs like `[[ -z "$variable" ]] || variable='foo'`. You also need it in places where a simple comment wouldn't parse properly, like an empty action in an if-then statement such as `if true; then; fi` (which raises `-bash: syntax error near unexpected token \`;'`), rather than `if true; then :; fi` (which runs without errors). – Todd A. Jacobs Dec 20 '18 at 14:05
11

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.

Felix Leipold
  • 1,064
  • 10
  • 17
5

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: How to check if a variable is set in Bash?

Community
  • 1
  • 1
kevinarpe
  • 20,319
  • 26
  • 127
  • 154
  • 1
    Wouldn't it be better to use `[[ -n $var ]]` or `[[ $var ]]` instead of `[ ! -z "$var" ]`? Also FYI Bash has support for variable indirection since v2, so there is no need for eval here. E.g. `is_var_defined(){ if (( $# != 1 )); then echo "Expected exactly one argument: variable name as string, e.g., 'my_var'" >&2; return 1; fi; [[ -n ${!1:-} ]]; }` – Six Jan 23 '17 at 07:06
1

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
efa
  • 11
  • 1
0

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

variable_undefined=""

Then

if [ "${variable_undefined}" == "" ]; then
    variable="$(...)"
fi
Luc M
  • 16,630
  • 26
  • 74
  • 89
  • Dashes aren't valid variable name characters. It's part of the parameter expansion syntax that the OP is using. – Dennis Williamson Jul 06 '12 at 12:35
  • 2
    Then it is better to test with -z to check if the size of the string is zero – coelhudo Jul 06 '12 at 12:35
  • @coelhuedo ...but you can't expand an undefined variable in `set -u` mode in order to run it through `test -z`, hence this question. – Charles Duffy Jul 06 '12 at 13:12
  • I like this way of getting around the problem because it forces you to 'declare' your variables at the top of your script. – Ramon Jul 06 '12 at 14:50
  • This doesn't work when you need to use environment variables set by other processes. – al. Jun 12 '13 at 09:53
0
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.)

George
  • 21
  • 1