Yolo Yolovich
Yolo Yolovich

Reputation: 35

Replace nested for loops in bash

can you help me getting rid off the nested for loop? Array may change all the time, as of right now i need to add/remove for loops. Is it possible with pure bash to replace nested for loops with more elegant solution?

#!/bin/bash

i=0
array=(A B C)
places=3
word1=word1
word2=word2
arrC=${#array[*]}
while [ $i -lt $((arrC**places)) ]; do
    for a in ${!array[*]}; do
        for b in ${!array[*]}; do
            for c in ${!array[*]}; do
                echo "[$i] ${array[$a]}${word1}${array[$b]}${word2}${array[$c]}"
                i=$((i + 1))
            done
        done
    done
done

I've made ABC array for simplicity. Here is more of a real example for an array: array=(I i L l | \ / 1 !)

Upvotes: 0

Views: 130

Answers (2)

markp-fuso
markp-fuso

Reputation: 34324

Looking at an awk solution that uses a recursive function to allow for a dynamic number of for loops.

But first a couple mods to the input parameters (to make it easier to pass info into awk); we'll replace bash level arrays with simple variables (using a : as a delimiter for this exercise):

array_s="A:B:C:D"
words_s="${word1}:${word2}"

One awk idea:

awk -v places="${places}" -v words_s="${words_s}" -v array_s="${array_s}" '

function print_place(place,outstring, j, sfx) {        # define but do not pass "j" and "sfx"; 
                                                       # this designates these as local variables

if ( place > places )                                  # if we haveve exceeded the number of places ...
   { i++                                               # increment our counter
     printf "%s\n", outstring                          # print our string to stdout
     return                                            # and exit this invocation of the function
   }
if ( i > total ) return                                # if we have exceeded total number of outputs
                                                       # then exit this invocation of the function

sfx=""                                                 # default suffix is "" unless we
if ( place < places ) sfx=words[place]                 # have not reached the max number of places

for ( j=1 ; j<=n ; j++ )                               # loop through arr[] entries
    print_place(place+1,outstring arr[j] sfx)          # recursive function call for place+1; also pass along 
                                                       # our growing string
}

BEGIN { n=split(array_s,arr,":")                       # parse "array_s" into awk array "arr[]"
        m=split(words_s,words,":")                     # parse "words_s" into awak array "words[]"
        total=n**places                                # calculate total number of strings to print

        i=0                                            # init counter
        print_place(1,"")                              # call function
}
'

Some sample runs:

places=3
array_s="A:B:C"
words_s="word1:word2"

awk ...

Aword1Aword2A
Aword1Aword2B
Aword1Aword2C
Aword1Bword2A
Aword1Bword2B
Aword1Bword2C
...snip...
Cword1Cword2A
Cword1Cword2B
Cword1Cword2C                        # 27 lines of output

##############

places=2
array_s="A:B:C:D"
words_s="word1:word2"

awk ...

Aword1A
Aword1B
Aword1C
Aword1D
Bword1A
Bword1B
Bword1C
Bword1D
Cword1A
Cword1B
Cword1C
Cword1D
Dword1A
Dword1B
Dword1C
Dword1D                        # 16 lines of output

##############

places=4
array_s="A:B:C:D"
words_s="word1:word2:word3"

awk ...

Aword1Aword2Aword3A
Aword1Aword2Aword3B
Aword1Aword2Aword3C
Aword1Aword2Aword3D
Aword1Aword2Bword3A
Aword1Aword2Bword3B
Aword1Aword2Bword3C
Aword1Aword2Bword3D
Aword1Aword2Cword3A
Aword1Aword2Cword3B
...snip...
Dword1Dword2Cword3D
Dword1Dword2Dword3A
Dword1Dword2Dword3B
Dword1Dword2Dword3C
Dword1Dword2Dword3D                        # 256 lines of output

Upvotes: 0

choroba
choroba

Reputation: 241858

Use recursion.

#! /bin/bash
array=(A B C)
words=(word1 word2)

combine () {
    local depth=$1
    local prefix=${2# }
    shift 2
    if ((depth == ${#array[@]})) ; then
        echo "$prefix"
        return
    fi

    local i
    for (( i=1; i<=$#; ++i )) ; do
        combine $((depth+1)) "$prefix${!i}${words[depth]}" "${array[@]}"
    done
}

combine 0 "" "${array[@]}"

Upvotes: 1

Related Questions