Wes Modes
Wes Modes

Reputation: 2136

Execute an arbitrary number of nested loops in bash

I am trying to iterate through an n-dimensional space with a series of nested for-loops in bash.

VAR1="a b c d e f g h i"
VAR2="1 2 3 4 5 6 7 8 9"
VAR3="a1 b2 b3 b4 b5 b6"

for i1 in $VAR1; do
    for i2 in $VAR2; do
        for i3 in $VAR3; do
            echo "$i1 $i2 $i3"
         done
    done
done

Now as I get more dimensions to iterate through, I realize it would be easier/better to be able to specify an arbitrary number of variables to loop through.

If I were using a more sophisticated programming language, I might use recursion to pass a list of lists to a function, pop one list off, iterate through it, recursively calling the function each time through the loop, passing the now reduced list of lists, and assembling the n-tuples as I go.

(I tried to pseudocode what that would look like, but it hurt my head thinking about recursion and constructing the lists.)

function iterate_through(var list_of_lists)
    this_list=pop list_of_lists
    var new_list = []
    for i in this_list
        new_list.push(i)
        new_list.push(iterate_through(list_of_lists))
     # return stuff
     # i gave up about here, but recursion may not even be necessary

Anyone have a suggestion for how to accomplish iterating through an arbitrary number of vars in bash? Keeping in mind the goal is to iterate through the entire n-dimensional space, and that iteration is not necessarily part of the solution.

Upvotes: 1

Views: 300

Answers (2)

Bertrand Martel
Bertrand Martel

Reputation: 45372

You can use recursion to compute cartesian product

The following script will do the job with variable length input vector :

#!/bin/bash

dim=("a b c d e f g h i" "1 2 3 4 5 6 7 8 9" "a1 b2 b3 b4 b5 b6")

function iterate {

    local index="$2"

    if [ "${index}" == "${#dim[@]}" ]; then

        for (( i=0; i<=${index}; i++ ))
        do
            echo -n "${items[$i]} "
        done
        echo ""
    else
        for element in ${dim[${index}]}; do
            items["${index}"]="${element}"
            local it=$((index+1))
            iterate items[@] "$it"
        done
    fi
}

declare -a items=("")

iterate "" 0

The following gist will take as input arguments all your dimensions array (with space separated items) : https://gist.github.com/bertrandmartel/a16f68cf508ae2c07b59

Upvotes: 1

ewcz
ewcz

Reputation: 13087

If parallel is acceptable, then one could simplify the nested for loop as

parallel -P1 echo {1} {2} {3} ::: $VAR1 ::: $VAR2 ::: $VAR3

In the general case, it could be perhaps feasible to first assemble this command and then execute it...

Upvotes: 1

Related Questions