3

I want to iterate over a few lists in bash. Right now I have

array=("list1item1 list1item2" "list2item list2item2")

for list in "${array[@]}"
do
    for item in $list
    do
        echo $item
    done
done

This doesn't work. Is there any way to make a list of lists, array of arrays, or array of lists in bash?

I want to iterate over list1, then the listitems in list1. Then iterate over list2, and the list items in list2.

Celi Manu
  • 371
  • 2
  • 7
  • 19
  • What do you define as "not working"? I get 4 lines of output, in the expected order. There are issues with your approach, but with the sample data, it seems to work. – Fred Jan 31 '17 at 19:43
  • Related: [multi-dimensional arrays in BASH](http://stackoverflow.com/questions/11233825/multi-dimensional-arrays-in-bash/) – Jose Ricardo Bustos M. Jan 31 '17 at 19:44

2 Answers2

12

Once I added the missing do and done into your code:

array=("list1item1 list1item2" "list2item list2item2")

for list in "${array[@]}"
do
    for item in $list
    do
        echo $item
    done
done

it produced the expected output:

list1item1
list1item2
list2item
list2item2

It's not clear to me how this differs from your expectation.

However, that is not a very general way of nesting a list into an array, since it depends on the internal list being IFS-separated. Bash does not offer nested arrays; an array is strictly an array of strings, and nothing else.

You can use indirection (${!v}) and store variable names into your outer array, although it is a bit ugly. A less ugly variant is the following, which relies on namerefs; it will work with reasonably recent bash versions:

array=(list1 list2)
list1=("list 1 item 1" "list 1 item 2")
list2=("list 2 item 1" "list 2 item 2")
for name in "${array[@]}"; do
  declare -n list=$name
  for item in ${list[@]}; do
    echo "$item"
  done
done

Output:

list 1 item 1
list 1 item 2
list 2 item 1
list 2 item 2
rici
  • 234,347
  • 28
  • 237
  • 341
5

lacking reputation to comment on rici...

It is now possible to create list of lists, more or less, and avoid using namereferences for this as in rici's answer(from bash4.4 and onwards), thanks to array quote expansion: @Q. For example:

declare -a list1=("one" "two three")
declare -a list2=("four five" "six")
declare -a listOfLists=("(${list1[*]@Q})" "(${list2[*]@Q})")
echo "${#listOfLists[@]}"
2

As you can see the listOfLists expands correctly to 2 elements. Now the nice thing is that thanks to @Q, the elements inside listOfLists which are lists, will also expand correctly to 2 elements each (instead of the 3 elements it would have without using @Q):

declare -a sameAsList1="${listOfLists[0]}"; declare -a sameAsList2="${listOfLists[1]}"
echo "${#sameAsList1[@]}" ; echo "${#sameAsList2[@]}"
2
2
declare -p sameAsList1 && declare -p list1
declare -a sameAsList1=([0]="one" [1]="two three")
declare -a list1=([0]="one" [1]="two three")

We got list of lists, finally!

methuselah-0
  • 96
  • 1
  • 5