bash

Indirect variable references in Bash

I’m writing a lot of Bash scripts at the moment, and I do a lot with environment variables which my scripts expect to have been set (by my CI/CD tool).

To stop me accidentally running the scripts without the variables having any values, I started off with this sort of thing:

if [ -z "${IMAGE_NAME}" ]; then
  echo "IMAGE_NAME must be set" >&2
  exit 1
fi
if [ -z "${IMAGE_TAG}" ]; then
  echo "IMAGE_TAG must be set" >&2
  exit 1
fi

For scripts with more than a couple of variables to check, this is pretty ugly. Wouldn’t it be nice, I thought, if I could write something like this?

assert_variables_set IMAGE_NAME \
                     IMAGE_TAG \
                     ...

A bit of searching led me to the Indirect References page of Mendel Cooper’s Advanced Bash-Scripting Guide. It provided extensive discussion and examples of using eval with a \$$name pattern, which led to something like this:

assert_variables_set() {
  for varname in "$@"; do
    if [ -z "$(eval echo \$$varname)" ]; then
      echo "$varname must be set" >&2
      exit 1
    fi
  done
}

This worked, but seemed a bit clunky. Then I noticed a tiny little comment:

A more straightforward method is the ${!t} notation, discussed in the “Bash, version 2” section.

Indeed: on the page about Bash version 2 there is an example Indirect variable references - the new way which shows how this works. My simplified version:

> IMAGE_TAG=3.5
> echo ${IMAGE_TAG}
3.5
> varname=IMAGE_TAG
> echo ${varname}
IMAGE_TAG
> echo ${!varname}
3.5

The end result looked a little less clunky:

assert_variables_set() {
  for varname in "$@"; do
    if [ -z "${!varname}" ]; then
      echo "$varname must be set" >&2
      exit 1
    fi
  done
}

I certainly don’t think this syntax is intuitive, but I think it’s probably better than the eval version.


Image credit: Shell script points of interest to coordinates by Christiaan Colen is licensed for reuse under CC BY-SA 2.0