Loop over tuples in bash?

BashFor Loop

Bash Problem Overview


Is it possible to loop over tuples in bash?

As an example, it would be great if the following worked:

for (i,j) in ((c,3), (e,5)); do echo "$i and $j"; done

Is there a workaround that somehow lets me loop over tuples?

Bash Solutions


Solution 1 - Bash

$ for i in c,3 e,5; do IFS=","; set -- $i; echo $1 and $2; done
c and 3
e and 5

About this use of set (from man builtins):

> Any arguments remaining after option processing are treated as values > for the positional parameters and are assigned, in order, to $1, $2, > ... $n

The IFS="," sets the field separator so every $i gets segmented into $1 and $2 correctly.

Via this blog.

Edit: more correct version, as suggested by @SLACEDIAMOND:

$ OLDIFS=$IFS; IFS=','; for i in c,3 e,5; do set -- $i; echo $1 and $2; done; IFS=$OLDIFS
c and 3
e and 5

Solution 2 - Bash

Based on the answer given by @eduardo-ivanec without setting/resetting the IFS, one could simply do:

for i in "c 3" "e 5"
do
    set -- $i # convert the "tuple" into the param args $1 $2...
    echo $1 and $2
done

The output:

c and 3
e and 5

Solution 3 - Bash

This bash style guide illustrates how read can be used to split strings at a delimiter and assign them to individual variables. So using that technique you can parse the string and assign the variables with a one liner like the one in the loop below:

for i in c,3 e,5; do 
    IFS=',' read item1 item2 <<< "${i}"
    echo "${item1}" and "${item2}"
done

Solution 4 - Bash

Use associative array (also known as dictionary / hashMap):

animals=(dog cat mouse)
declare -A sound=(
  [dog]=barks
  [cat]=purrs
  [mouse]=cheeps
)
declare -A size=(
  [dog]=big
  [cat]=medium
  [mouse]=small
)
for animal in "${animals[@]}"; do
  echo "$animal ${sound[$animal]} and it is ${size[$animal]}"
done

Solution 5 - Bash

c=('a' 'c')
n=(3    4 )

for i in $(seq 0 $((${#c[*]}-1)))
do
    echo ${c[i]} ${n[i]}
done

Might sometimes be more handy.

To explain the ugly part, as noted in the comments:

seq 0 2 produces the sequence of numbers 0 1 2. $(cmd) is command substitution, so for this example the output of seq 0 2, which is the number sequence. But what is the upper bound, the $((${#c[*]}-1))?

$((somthing)) is arithmetic expansion, so $((3+4)) is 7 etc. Our Expression is ${#c[*]}-1, so something - 1. Pretty simple, if we know what ${#c[*]} is.

c is an array, c[*] is just the whole array, ${#c[*]} is the size of the array which is 2 in our case. Now we roll everything back: for i in $(seq 0 $((${#c[*]}-1))) is for i in $(seq 0 $((2-1))) is for i in $(seq 0 1) is for i in 0 1. Because the last element in the array has an index which is the length of the Array - 1.

Solution 6 - Bash

$ echo 'c,3;e,5;' | while IFS=',' read -d';' i j; do echo "$i and $j"; done
c and 3
e and 5

Solution 7 - Bash

Using GNU Parallel:

parallel echo {1} and {2} ::: c e :::+ 3 5

Or:

parallel -N2 echo {1} and {2} ::: c 3 e 5

Or:

parallel --colsep , echo {1} and {2} ::: c,3 e,5

Solution 8 - Bash

But what if the tuple is greater than the k/v that an associative array can hold? What if it's 3 or 4 elements? One could expand on this concept:

###---------------------------------------------------
### VARIABLES
###---------------------------------------------------
myVars=(
    'ya1,ya2,ya3,ya4'
    'ye1,ye2,ye3,ye4'
    'yo1,yo2,yo3,yo4'
    )

 
###---------------------------------------------------
### MAIN PROGRAM
###---------------------------------------------------
### Echo all elements in the array
###---
printf '\n\n%s\n' "Print all elements in the array..."
for dataRow in "${myVars[@]}"; do
    while IFS=',' read -r var1 var2 var3 var4; do
        printf '%s\n' "$var1 - $var2 - $var3 - $var4"
    done <<< "$dataRow"
done

Then the output would look something like:

$ ./assoc-array-tinkering.sh 

Print all elements in the array...
ya1 - ya2 - ya3 - ya4
ye1 - ye2 - ye3 - ye4
yo1 - yo2 - yo3 - yo4

And the number of elements are now without limit. Not looking for votes; just thinking out loud. REF1, REF2

Solution 9 - Bash

do echo $key $value
done < file_discriptor

for example:

$ while read key value; do echo $key $value ;done <<EOF
> c 3
> e 5
> EOF
c 3
e 5

$ echo -e 'c 3\ne 5' > file

$ while read key value; do echo $key $value ;done <file
c 3
e 5

$ echo -e 'c,3\ne,5' > file

$ while IFS=, read key value; do echo $key $value ;done <file
c 3
e 5

Solution 10 - Bash

Using printf in a process substitution:

while read -r k v; do
    echo "Key $k has value: $v"
done < <(printf '%s\n' 'key1 val1' 'key2 val2' 'key3 val3')

Key key1 has value: val1
Key key2 has value: val2
Key key3 has value: val3

Above requires bash. If bash is not being used then use simple pipeline:

printf '%s\n' 'key1 val1' 'key2 val2' 'key3 val3' |
while read -r k v; do echo "Key $k has value: $v"; done

Solution 11 - Bash

A bit more involved, but may be useful:

a='((c,3), (e,5))'
IFS='()'; for t in $a; do [ -n "$t" ] && { IFS=','; set -- $t; [ -n "$1" ] && echo i=$1 j=$2; }; done

Solution 12 - Bash

In cases where my tuple definitions are more complex, I prefer to have them in a heredoc:

while IFS=", " read -ra arr; do
  echo "${arr[0]} and ${arr[1]}"
done <<EOM
c, 3
e, 5
EOM

This combines looping over lines of a heredoc with splitting the lines at some desired separating character.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionFrankView Question on Stackoverflow
Solution 1 - BashEduardo IvanecView Answer on Stackoverflow
Solution 2 - BashMZHmView Answer on Stackoverflow
Solution 3 - BashGrant HumphriesView Answer on Stackoverflow
Solution 4 - BashVasiliNovikovView Answer on Stackoverflow
Solution 5 - Bashuser unknownView Answer on Stackoverflow
Solution 6 - BashkevView Answer on Stackoverflow
Solution 7 - BashOle TangeView Answer on Stackoverflow
Solution 8 - Bashtodd_dsmView Answer on Stackoverflow
Solution 9 - Bashprodriguez903View Answer on Stackoverflow
Solution 10 - BashanubhavaView Answer on Stackoverflow
Solution 11 - BashDiego Torres MilanoView Answer on Stackoverflow
Solution 12 - Bashbluenote10View Answer on Stackoverflow