Loop over tuples in bash?

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?

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

关于 set的使用(来自 man builtins) :

选项处理后剩余的任何参数都被视为值 for the positional parameters and are assigned, in order, to $1, $2, ... $n

IFS=","设置字段分隔符,这样每个 $i都被正确地分割成 $1$2

通过 这个博客

编辑: 更正版本,由@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

有点复杂,但可能有用:

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


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

也许有时候更方便。

如评论所述,解释 ugly部分:

序列02 产生数列012。$(cmd)是指令替代的,因此在本例中,输出的是数字序列 seq 0 2。但是 $((${#c[*]}-1))的上界是什么呢?

$((something))是算术扩展,所以 $((3 + 4))是7等等。我们的表达式是 ${#c[*]}-1,所以是 -1。很简单,如果我们知道 ${#c[*]}是什么的话。

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.

这个 bash 样式指南演示了如何使用 read在分隔符处拆分字符串并将它们分配给各个变量。因此,使用这种技术,你可以解析字符串,然后用一行代码分配变量,就像下面循环中的一行代码那样:

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

使用 GNU 并行:

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

或者:

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

或者:

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

使用关联数组(也称为 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

根据@eduardo-ivanec 给出的答案,在不设置/重置 IFS的情况下,人们可以简单地这样做:

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

输出:

c and 3
e and 5
do echo $key $value
done < file_discriptor

例如:

$ 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

在进程替代中使用 printf:

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

以上要求使用 bash。如果没有使用 bash,那么使用简单的管道:

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

但是如果元组大于关联数组所能承受的 k/v 呢?如果是3或4个元素呢?我们可以扩展这个概念:

###---------------------------------------------------
### 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

然后输出会是这样的:

$ ./assoc-array-tinkering.sh


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

现在元素的数量是没有限制的。不是寻找投票,只是大声地思考。 参考1参考文献2

在元组定义比较复杂的情况下,我更喜欢将它们放在 herdoc 中:

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

它将 looping over lines of a heredoc在某个期望的分隔字符处分割线结合在一起。