Reputation: 9804
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
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
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
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