2

My script has a bunch of changing variables that start with the string "versions_" so for example:

versions_a=("1" "2")
versions_b=("3" "4")

I want to loop through these variables and access the underlying arrays:

#!/usr/bin/env bash
versions_a=("1" "2")
versions_b=("3" "4")

for i in ${!versions_*}; do

#Ideally if statement here if underlying array is empty, continue to next iteration

echo "${i} contains ${i[*]}"
versions=(${!i})
echo "versions has ${versions[@]}"

for x in "${versions[@]}"; do 
  echo ${x} #should print 1, then 2, then 3, then 4
done

done

Expected output:

versions_a has 1 2
versions has 1 2
1
2
versions_b has 3 4
versions has 3 4
3
4

Current output:

versions_a contains versions_a
versions has 1
1
versions_b contains versions_b
versions has 3
3
  • 1
    Which bash version is that supposed to be? My 5.2.15 gives line 6: ${versions_*}: bad substitution – Hauke Laging May 19 '23 at 18:18
  • @HaukeLaging fixed typo was missing the !. I'm on bash 4.2 – CeePlusPlus May 19 '23 at 18:32
  • Can you explain what the end objective is here? Why are you using this approach, what do you want to achieve? This feels very much like an XY problem. Could you, for example, use for i in a b c d;... to know which array to look at? Or perhaps you want to use associative instead of indexed arrays? – terdon May 19 '23 at 18:54
  • abcd are undefined and change from script to script so the proper name is unknown to rest of the script. I'm fine with another approach, but the main point is that I need to access the individual elements in several variables, and I only know the variables start with versions_. – CeePlusPlus May 19 '23 at 19:03
  • Later in script the abcd gets absorbed into the actual trigger (i.e. run a+1 a+2 b+3 ....) – CeePlusPlus May 19 '23 at 19:10
  • 1
    In case you were wondering, why you get the same value twice in echo "${i} contains ${i[*]}", it's because scalars and ksh-style arrays are somewhat interchangeable. $i is the same ${i[0]}, and similarly ${i[*]} just joins the single element of the "array" to a string. for i in ... only fills the element at index 0. – ilkkachu May 19 '23 at 20:15
  • and, well, since what you're basically doing is a two-dimensional array, you might want to consider switching to another programming language, one with better support for complex data structures. Or at least to ksh, which can do two directional arrays (or at least lists of lists). – ilkkachu May 19 '23 at 20:16

2 Answers2

4

Easiest is to use a nameref here (assuming bash 4.3 or newer):

#! /bin/bash -

versions_a=( 1 2 ) versions_b=( 3 4 ) versions_c=( -n '*' )

for varname in "${!versions_@}"; do typeset -n versions="$varname"

IFS=, printf '%s\n' "$varname contains ${versions[*]}"

for x in "${versions[@]}"; do printf '%s\n' "$x" done done

Which gives:

versions_a contains 1,2
1
2
versions_b contains 3,4
3
4
versions_c contains -n,*
-n
*

Also remember that parameter expansions should be quoted and that echo can't be used to output arbitrary data. And that the expansion of "$*" or "${array[*]}" depends on the current value of $IFS (here demonstrated by setting it to ,).

3

See this answer:

The tricky thing is that you have to include the array element (or [@] for all elements) in the variable you're indirecting through.

… or [*] if this is what you want. Something like this:

#!/usr/bin/env bash
versions_a=("1" "2")
versions_b=("3" "4")
versions_c=()

for i in ${!versions_*}; do

group=${i#*"_"} #removes the 'versions_'
versions="${i}[@]"
last_version=${!versions: -1}

if [[ -z ${last_version} ]]; then
    echo "Empty, skipping."
    continue
fi

echo "${i} as ${group} has ${!versions}"

for x in "${!versions}"; do 
  echo "${group} & ${x}"
done

done

Output

versions_a as a has 1 2
a & 1
a & 2
versions_b as b has 3 4
b & 3
b & 4
Empty, skipping.
Lucas
  • 2,845
  • More precisely wholestring here is the concatenation of the elements of the array using the first character of $IFS inbetween, or nothing if $IFS is empty or a space character if $IFS is unset. – Stéphane Chazelas May 19 '23 at 19:36
  • @HaukeLaging thank you for your attempt was very useful (and timely). – CeePlusPlus May 19 '23 at 19:44
  • @Kamil Maciorowski def simpler and seems to be working, will test a bit more... – CeePlusPlus May 19 '23 at 19:44
  • 1
    @CeePlusPlus Unfortunately I don't know (yet?) how to get the number of elements this way. A workaround is to define a function that counts its elements: c() { echo "$#"; } ; then c "${!wholearray}" should print the number and you can capture it using command substitution. Quite inelegant though. – Kamil Maciorowski May 19 '23 at 19:46
  • @KamilMaciorowski later in the script I have to check what the last version is in the list, so I moved that check up. I edited your script a bit to simply check if that variable is empty. Feel free to adjust my additions. – CeePlusPlus May 19 '23 at 21:31