Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Loop over tuples in bash?

Tags:

bash

for-loop

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?

like image 206
Frank Avatar asked Mar 15 '12 02:03

Frank


10 Answers

$ 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 
like image 67
Eduardo Ivanec Avatar answered Sep 20 '22 13:09

Eduardo Ivanec


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 
like image 21
Grant Humphries Avatar answered Sep 17 '22 13:09

Grant Humphries


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
like image 28
MZHm Avatar answered Sep 19 '22 13:09

MZHm


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
like image 22
VasiliNovikov Avatar answered Sep 18 '22 13:09

VasiliNovikov


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.

like image 43
user unknown Avatar answered Sep 18 '22 13:09

user unknown


$ echo 'c,3;e,5;' | while IFS=',' read -d';' i j; do echo "$i and $j"; done
c and 3
e and 5
like image 42
kev Avatar answered Sep 20 '22 13:09

kev


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
like image 35
Ole Tange Avatar answered Sep 21 '22 13:09

Ole Tange


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

like image 22
todd_dsm Avatar answered Sep 20 '22 13:09

todd_dsm


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
like image 42
prodriguez903 Avatar answered Sep 21 '22 13:09

prodriguez903


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
like image 35
anubhava Avatar answered Sep 19 '22 13:09

anubhava