Ashraf Bashir
Ashraf Bashir

Reputation: 9804

Looping over array elements, given an array of arrays' variable names in Bash Script

I have an array containing names of categories

categories=( categoryA categoryB categoryC )

Each category contains multiple products, which is stored in different arrays.

categoryA=( productA1 productA2 productA3 )
categoryB=( productB1 productB2 productB3 )
categoryC=( productC1 productC2 productC3 )

I want to loop over all products, so I wrote this code:

categories=( categoryA categoryB categoryC )

categoryA=( productA1 productA2 productA3 )
categoryB=( productB1 productB2 productB3 )
categoryC=( productC1 productC2 productC3 )

for category_name in "${categories[@]}"
do
    echo $category_name

    category=${!category_name}
    for product in "${category[@]}" 
    do
        echo -e '\t' $product
    done

    echo -e '\n'

done

I expected the output to be:

CategoryA
    ProductA1  
    ProductA2
    ProductA3

CategoryB
    ProductB1  
    ProductB2
    ProductB3

CategoryC
    ProductC1  
    ProductC2
    ProductC3

but unfortunately, the output is

CategoryA
    ProductA1  

CategoryB
    ProductB1  

CategoryC
    ProductC1  

How to fix this ?

Upvotes: 2

Views: 59

Answers (3)

Ashraf Bashir
Ashraf Bashir

Reputation: 9804

I already found a solution,

instead of category=${!category_name} ,
I can eval the array eval category=\( \${${category_name}[@]} \)

for those who are interested, here it is:

for category_name in "${categories[@]}"
do
    echo $category_name

    eval category=\( \${${category_name}[@]} \)
    for product in "${category[@]}" 
    do
        echo -e '\t' $product
    done

    echo -e '\n'

done

I will leave the question and the answer, for whoever meets the same challenge, and for anyone who has a more elegant solution, or for any objections


UPDATE: For security considerations, check Inian's response, and if you use Bash < 4.3 as I do, check Leon's response as an alternative

Upvotes: 0

Leon
Leon

Reputation: 32484

If Inian's answer doesn't work for you because your are using a version of bash older than 4.3 (where the support for nameref variables was introduced), you can go for the following solution:

categories=( categoryA categoryB categoryC )

categoryA=( productA1 productA2 productA3 )
categoryB=( productB1 productB2 productB3 )
categoryC=( productC1 productC2 productC3 )

for category_name in "${categories[@]}"
do
    echo $category_name


    array_declaration="$(declare -p $category_name)"
    # "$array_declaration" expands to the command that can be (safely)
    # evaluated to recreate the $category_name variable 

    # change the variable name in the array declaration and eval it
    eval "${array_declaration/#declare -a "$category_name"=/declare -a category=}"

    for product in "${category[@]}"
    do
        echo -e '\t' $product
    done

    echo -e '\n'

done

Note that reliance on eval in the above code should be safe, since the output of declare -p is properly quoted.

Upvotes: 1

Inian
Inian

Reputation: 85580

Absolutely do NOT use eval for this case, just use declare to create variables on the fly,

Just replace the line

category=${!category_name}

in your original answer with

declare -n category="${category_name}"

the above declare syntax will create a reference for category to the variable category_name which can be accessed further for expanding as an array.


declare -n is a relatively newer syntax available since introduced in Bash 4.3-alpha

Upvotes: 1

Related Questions