This question comes from a desire to reduce some repetition in a bash script, around checking if we have something in a file and if so, loading the first line of it into a variable of the same name.
At the moment, there are dozens of lines like this for each variable used throughout:
[[ -s './config/foo' ]] && read -r foo < './config/foo' || echo "problem with ./config/foo file"
[[ -s './config/bar' ]] && read -r foo < './config/bar' || echo "problem with ./config/foo file"
But rather than going through way too many lines like this, I thought I could maybe automate this process by using an array (and also later, expand to other tests with this method).
So I started on the code below, but got stuck at wondering if it's possible to create variable names dynamically? I have no idea how to do that, or if it's even possible. I know I can get the name of the file we would want to use for making the variable name by using ${file##*/}
to strip off the paths (so ./config/foo
becomes foo
for example), but how to turn that result into a variable name and then set it to the contents of the first line of a file, like the original script does?
Here's what I have so far, where DYNAMIC_VARIABLE_NAME_HERE
would be the name we can get from ${file##*/}
:
#!/bin/bash
# check and load config files
required_files=(
'./config/foo'
'./config/bar'
)
for file in "${required_files[@]}"; do
[[ -s "${file}" ]] && read -r DYNAMIC_VARIABLE_NAME_HERE < "${file}" || failed+=("${file}")
done
if [[ ${#failed[@]} -ne 0 ]]; then
echo "there is a problem with the following configuration files:"
for file in "${failed[@]}"; do
echo "${file}"
done
fi
# check
echo "foo: ${foo}"
echo "bar: ${bar}"
foo:
bar:
foo: [first line of ./config/foo file]
bar: [first line of ./config/bar file]
Setup:
$ head foo bar
==> foo <==
1st line from foo
2nd line from foo
==> bar <==
1st line from bar
2nd line from bar
If using bash 4.3+
you could use a nameref, eg:
for fname in foo bar
do
declare -n curr_var="${fname}" # nameref
read -r curr_var < "${fname}"
done
This generates:
$ typeset -p foo bar
declare -- foo="1st line from foo"
declare -- bar="1st line from bar"
One problem with this approach is ... how do you keep track of the dynamically generated variable names. In this case we've hardcoded the variable names foo
and bar
in the code, but what if there were 10 files, and we don't know (programmatically) beforehand what the file/variable names are ... how/where do you keep track of the variable names?
A different approach using an associative array:
unset myvar # insure array name not in use
declare -A myvar # define Associative array
for fname in * # for now assume this matches on files "foo" and "bar"
do
read -r myvar[$fname] < "${fname}"
done
This generates:
$ typeset -p myvar
declare -A myvar=([bar]="1st line from bar" [foo]="1st line from foo" )
Furthermore, we can get a list of our variable names by perusing the indices of the array, eg:
for varname in "${!myvar[@]}" # loop through array indices
do
echo "varname = ${varname} : contents: ${myvar[$varname]}"
done
This generates:
varname = bar : contents: 1st line from bar
varname = foo : contents: 1st line from foo