1

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}"

Output so far

foo:
bar:

Desired output

foo: [first line of ./config/foo file]
bar: [first line of ./config/bar file]
nooblag
  • 678
  • 3
  • 23
  • 2
    It's possible, but why do it if you can use an associative array? – Barmar Aug 09 '23 at 23:33
  • Thanks for your comment. Unfortunately, you might have to help me understand what you mean... Sorry! Is there a different approach that could achieve the same result? I'm of course open to that :) – nooblag Aug 09 '23 at 23:42
  • 1
    You said it in the question: "I thought I could maybe automate this process by using an array". So just use an array rather than dynamic variables. `array[$file]` – Barmar Aug 09 '23 at 23:58
  • Maybe `mytFileToProcess=${file##*/}` would help you (right at the top of your `for` loop, then reference that instead of `$file`. OR I don't understand your requirement. Good luck! – shellter Aug 10 '23 at 00:01
  • Does this answer your question? [Dynamic variable names in Bash](https://stackoverflow.com/questions/16553089/dynamic-variable-names-in-bash) – pjh Aug 10 '23 at 00:05
  • 3
    See [BashFAQ/006 (How can I use variable variables (indirect variables, pointers, references) or associative arrays?)](https://mywiki.wooledge.org/BashFAQ/006). – pjh Aug 10 '23 at 00:06

1 Answers1

4

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.2+ 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
markp-fuso
  • 28,790
  • 4
  • 16
  • 36
  • 2
    Great! I think the associative array approach looks much cleaner and more manageable. Thanks for the insight! – nooblag Aug 10 '23 at 00:09