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