Sonntag, 9. November 2014

Combining the Bash nounset option with indirect expansion

Bash has a useful option called nounset. The option terminates the script if a variable is use, which has not been initialized. Using uninitialized values is a common error in script languages. Perl has introduced the strict package to address this kind of error and it is good Perl programing style to use it. The same applies to Bash: make set -u the first line after the shebang.

If you do so it becomes a bit more complicated to check, if a variable is set. The standard test will not work any more:

set -u
if [ -z "$VAR" ]; then
  echo 'VAR has no value.'
fi

Bash will terminate the script, because VAR is not set and there is no way to catch this exception. This means you have to avoid triggering nounset. This can be done with Bash's parameter expansion, in particular the "use default value" variant. The following code will not fail anymore:

set -u
if [ -z "${VAR:-}" ]; then
  echo 'VAR has no value.'
fi

Now the expression expands to the default value behind the hyphen, which is an empty string.

If you like to put the code into a function, it can be done this way:

set -u
require_value ()
{
  local VAR=$1
  local VAL=${!VAR}
  if [ -z "$VAL" ]; then
    die "Required value missing: $VAR"
  fi
}

The function require_value does another parameter expansion, the indirect expansion. The function takes the name of a variable as an argument and checks if the variable with the specified name has a value. This works as long as you do not enable the nounset option. If you enable it the function fails while setting the variable VAL.

The Bash manual is not very detailed about the question how to combine two parameter expansions. Trying to put them into each other fails.

$ a=b; echo ${${!a}:-}
bash: ${${!a}:-}: bad substitution

The answer is, that you can combine both notations into one parameter expansion and it happens just the right thing, which means the indirection is performed first and after that the default value gets applied.

set -u
require_value ()
{
  local VAR=$1
  local VAL=${!VAR:-}
  if [ -z "$VAL" ]; then
    die "Required value missing: $VAR"
  fi
}

Keine Kommentare: