0

I'm not sure how to use a nested for loop in bash. How do I turn the below code into a nested for loop?

~/
❯ cat sequential.sh
a=(1 2)
b=(3 4)
c=(5 6)

for e in "${a[@]}" do echo $e done

for e in "${b[@]}" do echo $e done

for e in "${c[@]}" do echo $e done

✦ ~/ ❯ bash sequential.sh 1 2 3 4 5 6

geek
  • 177

2 Answers2

2

By "nesting" (in relation to loops), one usually means putting one loop into another, so that each set of iterations of the inner loop is carried out for each iteration of the outer loop, as in

for letter in a b c; do
    for digit in 1 2 3; do
        printf 'Letter = %s\tDigit = %s\n' "$letter" "$digit"
    done
done

... which outputs

Letter = a      Digit = 1
Letter = a      Digit = 2
Letter = a      Digit = 3
Letter = b      Digit = 1
Letter = b      Digit = 2
Letter = b      Digit = 3
Letter = c      Digit = 1
Letter = c      Digit = 2
Letter = c      Digit = 3

However, in your question, assuming you still want to have the same output, it does not appear as if you want to do any nesting. Instead, you want to combine your three separate loops into a single loop.

This can be done by simply adding the expansions of the arrays, one after the other, to the loop header to create a larger set of strings to iterate over:

a=(1 2)
b=(3 4)
c=(5 6)

for item in "${a[@]}" "${b[@]}" "${c[@]}" do printf 'item is "%s"\n' "$item" done

This has the same effect as creating a separate array consisting of the elements from the three smaller arrays, and the iterating over that larger array:

a=(1 2)
b=(3 4)
c=(5 6)

combined=( "${a[@]}" "${b[@]}" "${c[@]}" )

the above has the effect of setting the combined array as

combined=( 1 2 3 4 5 6 )

for item in "${combined[@]}"; do printf 'item is "%s"\n' "$item" done

Or, shorter, using the list of positional parameters,

a=(1 2)
b=(3 4)
c=(5 6)

set -- "${a[@]}" "${b[@]}" "${c[@]}"

for item do printf 'item is "%s"\n' "$item" done

Note that this is not nesting but instead combining three separate sets of array elements into a larger set.


If all we want to do is to output the elements of the three arrays using a minimum amount of code, we may want to use the fact that printf applies the format string to all arguments in turn until all its arguments have been outputted:

a=(1 2)
b=(3 4)
c=(5 6)

printf 'item is "%s"\n' "${a[@]}" "${b[@]}" "${c[@]}"


Another way to do this, which is a bit awkward but that does involve nesting loops, is to loop over the actual arrays and process each of them in turn. In the bash shell, you can do this by looping over the names of the arrays with a name reference variable:

a=(1 2)
b=(3 4)
c=(5 6)

for arrayname in a b c; do declare -n array="$arrayname" for item in "${array[@]}"; do printf 'item is "%s" (from original array "%s")\n' "$item" "$arrayname" done done

Here, we use declare -n to say that the variable array should behave like whatever variable arrayname holds the name of at the moment. We're then using array as if it was an array, which works because the values that $arrayname expands to are names of arrays.

This is a somewhat unusual way to program in bash, but it works. It also allows us to make the code a bit more elaborate by introducing a separate array-processing function (note that in this particular case, this is not an appropriate way to write this code, and it would be better to use a single printf statement and no loop at all, as shown above):

a=(1 2)
b=(3 4)
c=(5 6)

process_array () { local -n array="$1" for item in "${array[@]}"; do printf 'item is "%s" (from original array "%s")\n' "$item" "$arrayname" done }

for arrayname in a b c; do process_array "$arrayname" done

This is "passing an array to a function" (actually passing the name of a variable and then using that as the name of an array with a local name reference variable in the function).

Note that there are a few limitations to this. You can't, for example, call another function from process_array with a name reference variable called array. See also Circular name references in bash shell function, but not in ksh

Kusalananda
  • 333,661
-2
✦ ~/
❯ cat nested.sh
a=(1 2)
b=(3 4)
c=(5 6)

for arr in "${a[@]}" "${b[@]}" "${c[@]}"
do
    for e in "${arr[@]}"
    do
        echo $e
    done
done

✦ ~/
❯ bash nested.sh
1
2
3
4
5
6

✦ ~/
❯
  • The nesting in your code is purely artificial. The inner loop is not nececary as arr is a scalar variable, not an array. – Kusalananda Dec 27 '22 at 08:03
  • @Kusalananda that's not very clear. Feel free to add an answer. – Geoff Langenderfer Dec 27 '22 at 09:18
  • 1
    I think what he means is that in for arr in ..., the arr variable only ever gets a single (scalar) value at a time. It's not an array in any meaningful sense, other than the way the shells allow using the array and scalar syntaxes somewhat interchangeably. (I.e. $x is the same as ${x[0]}.) Using "${arr[@]}" here is somewhat misleading, IMO. – ilkkachu Dec 27 '22 at 15:42
  • @ilkkachu, why does it produce the same output? – Geoff Langenderfer Dec 27 '22 at 18:52
  • @GeoffLangenderfer, I have the impression that a lot of things in the (POSIX-like) shells are the way they are because someone did it that way back in the day, and they stuck due to compatibility reasons. (Same with word splitting.) I suppose allowing cross-use makes it somewhat easier to transition between using a scalar and an array, but I don't think it would be considered a good idea in modern times. Look into zsh and e.g. fish for more modern implementations with less historical baggage. – ilkkachu Dec 27 '22 at 19:36