Bash: loop through lists (array of arrays) and access sublist value

54 Views Asked by At

In BASH, consider a (pseudo)structured list of arrays like so:

## all my arrays definitions:
myArray1=("userList1" "emailList1" "valueList1")
myArray2=("userList2" "emailList2" "valueList2")
...
myArrayN=("userListN" "emailListN" "valueListN")

## a list of the arrays I need to loop through:
active_lists=("myArray1" "myArray2" [... "myArrayN"])

I want to loop through each of the arrays I've configured in my active_lists collection.

This is often mentioned in tutorials and one would assume the following would do the trick:

for indexd in "${active_lists[@]}"; do
   username=${!indexd}[0]
   email=${!indexd}[1]
   echo "Email: ${email}, username: ${username}"
done

Putting all this in a Bash script (with strict mode enabled) should be most straightforward:

#!/bin/bash

set -euo pipefail

declare -a myArray1=("userList1" "emailList1" "valueList1")
declare -a myArray2=("userList2" "emailList2" "valueList2")

declare -a active_lists=("myArray1" "myArray2")

for indexd in "${active_lists[@]}"; do
   username=${!indexd}[2]
   email=${!indexd}[1]
   echo "Email: ${email}, username: ${username}"

   sublist=(${!indexd})
   echo "List Value: ${sublist[1]}"
done

However running the above script results in literal printing of the elements names, or unbound variable warning when attempting to access an index in the sublist:

Email: userList1[1], username: userList1[2]
Email: userList2[1], username: userList2[2]

or

line 16: sublist[1]: unbound variable

This should be simple AF and I am obviously missing something fundamental but I cannot figure out what that is, and I'm going mad! Please help.

2

There are 2 best solutions below

1
markp-fuso On

While there may be a way to use indirect references with arrays (it's going to be a bit convoluted) I find namerefs are much simpler to use ...

With bash 4.3+ you can use a nameref to cycle through the different arrays.

One bash approach:

myArray1=("userList1" "emailList1" "valueList1")
myArray2=("userList2" "emailList2" "valueList2")
myArrayN=("userListN" "emailListN" "valueListN")

active_lists=("myArray1" "myArray2" "myArrayN")

for arr_name in "${active_lists[@]}"
do
    printf "\n####### ${arr_name}\n\n"

    declare -n _arr="${arr_name}"                      # nameref

    for i in "${!_arr[@]}"                             # loop through indices of nameref'd array "_arr[]" (ie, indices of the current "arr_name" array]
    do
        echo "${arr_name}[$i] = ${_arr[i]}"
    done
done

NOTES:

  • ${arr_name}[$i] is not an array reference but rather 4 strings appended together (${arr_name} + [ + $i + ]) for display purposes
  • ${_arr[i]} is an array reference and due to the nameref will display the ith element of the current arr_name array

This generates:

####### myArray1

myArray1[0] = userList1
myArray1[1] = emailList1
myArray1[2] = valueList1

####### myArray2

myArray2[0] = userList2
myArray2[1] = emailList2
myArray2[2] = valueList2

####### myArrayN

myArrayN[0] = userListN
myArrayN[1] = emailListN
myArrayN[2] = valueListN
3
Marius Cucuruz On

MarkP-Fuso, thank you very much for your reply! It is indeed the nameref that I couldn't get my head around (it's still a bit fuzzy tbh). This is all but an academic exercise, really.

To reiterate, all my initial script was missing was:

declare -n sublist="${indexd}"

Effectively I needed to declare a local copy of each of my arrays when looping through the array placeholder (i.e. active_lists). By doing declare -n sublist="${indexd}" I can read any indices in the array and assign them to local variables.


This is my working script:

#!/bin/bash

set -euo pipefail

declare -a myArray1=("userList1" "emailList1" "valueList1")
declare -a myArray2=("userList2" "emailList2" "valueList2")

declare -a active_lists=("myArray1" "myArray2")

for indexd in "${active_lists[@]}"; do
   declare -n sublist="${indexd}"

   username=${sublist[0]}
   email=${sublist[1]}

   echo "List ${indexd}: Email: ${email}, username: ${username} (${sublist[2]})"
done