在bash中遍历元组?


88

是否有可能在bash中遍历元组?

例如,如果以下方法有效,那就太好了:

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

有没有一种解决方法,可以通过某种方式让我遍历元组?


4
来自python背景,这确实是一个非常有用的问题!
约翰·姜

5
回顾这四年之后,我想知道是否还有更好的方法可以做到这一点。我的天啊。
Giszmo '16

将近8年后,我还想知道是否还有更好的方法可以做到这一点。但是这个2018年的答案对我来说看起来非常好:stackoverflow.com/a/52228219/463994
MountainX

Answers:


87
$ 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)的用法:

选项处理后剩余的所有参数将被视为位置参数的值,并依次分配给$ 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

7
很好-如果要指出的话,IFS应该保存并重置为原始值(如果它在命令行上运行)。另外,新值IFS可以在循环运行之前设置一次,而不是每次迭代设置一次。
Eggxactly 2012年

1
如果任何$ i以连字符开头,set -- $i
则更

1
无需保存IFS,只需为set命令设置:for i in c,3 e,5; do IFS="," set -- $i; echo $1 and $2; done。请编辑您的答案:如果所有读者只能选择列出的解决方案之一,则不必阅读完整的开发历史记录。感谢您的炫酷技巧!
cfi

如果我声明tuples="a,1 b,2 c,3"并放置IFS=','为已编辑版本中的,而不是c,3 e,5使用$tuples它,则根本无法很好地打印出来。但是,相反,如果我在for循环中IFS=','仅在do关键字之后放置,则在使用$tuples以及乱码值时都可以很好地工作。只是认为这是值得一提的。
Simonlbc

@Simonlbc是因为for循环用于IFS拆分迭代。即,如果您遍历一个数组arr=("c,3" "e,5"),并将其放在IFSfor循环之前,则for的值$i将是cand e,它将被分开35因此set将无法正确解析,因为$i将没有任何内容可以解析。这意味着,如果未内联要迭代的值,IFS则应将放在循环内,而外部值应遵循要迭代的变量的预期分隔符。在这种情况下,$tuples应该简单地IFS=将其设为默认值并在空白处分割。
撤消

25

我相信此解决方案比已提交的其他解决方案要干净一些,请参阅bash样式指南,以说明如何使用read在分隔符处分割字符串并将它们分配给各个变量。

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

17

根据@ eduardo-ivanec给出的答案,无需设置/重置IFS,只需执行以下操作:

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

输出:

c and 3
e and 5

在我看来,这种方法比公认和最受推崇的方法简单得多。与@Eduardo Ivanec的建议相反,有什么理由不这样做吗?
spurra

@spurra这个答案是6年,而最近的回答是½,并且基于此。归功于到期。
迭戈

1
@迭戈我知道这一点。它明确地写在答案中。我在问是否有任何理由不对接受的答案使用这种方法。
spurra

2
@spurra,如果默认分隔符(空格,制表符或换行符)由于某些原因对您不起作用(bash.cyberciti.biz/guide/$IFS),您想使用Eduardo的答案
Diego

11

使用关联数组(也称为Dictionary / hashMap):

declare -A pairs=(
  [c]=3
  [e]=5
)
for key in "${!pairs[@]}"; do
  value="${pairs[$key]}"
  echo "key is $key and value is $value"
done

适用于bash4.0 +。


如果需要三元组而不是对,可以使用更通用的方法:

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

仅供参考,在Mac上GNU bash, version 4.4.23(1)-release-(x86_64-apple-darwin17.5.0),该功能不适用于Mac ,而Mac是通过brew安装的,因此是YMMV。
大卫,

但是它确实GNU bash, version 4.3.11(1)-release-(x86_64-pc-linux-gnu)在Docker容器中的Ubuntu 14.04上运行。
大卫

似乎是较旧的bash版本或不支持此功能的版本可以在索引基础上工作?键是数字而不是字符串。tldp.org/LDP/abs/html/declareref.html,相反的-A,我们有-a
戴维(David)

大卫,看来是这样。我认为您可以尝试使用数组索引来获得“关联性”。诸如此类declare -a indices=(1 2 3); declare -a sound=(barks purrs cheeps); declare -a size=(big medium small)。尚未在终端中尝试过,但我认为它应该可以工作。
VasiliNovikov '18 -10-18

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

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

有时可能更方便。

ugly如注释中所述,解释该部分:

seq 0 2产生数字序列0 12。$(cmd)是命令替换,因此对于本示例,输出seq 0 2,即数字序列。但是上限是$((${#c[*]}-1))多少?

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

c是一个数组,c [*]只是整个数组,$ {#c [*]}是数组的大小,在我们的例子中为2。现在我们回滚所有内容: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。因为数组中的最后一个元素的索引是数组的长度-1。


1
您应该做for i in $(seq 0 $(($#c[*]}-1))); do [...]
reox

1
哇,这获得了“我今天见过的最丑陋的任意角色”奖。有人在乎解释这种可憎行为的确切含义吗?我
迷上了井

1
@koniiiik:添加了说明。
用户未知

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

3

使用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

1
不爱这个吗?好让克服了惯性并进行了安装gnu parallel
StephenBoesch

2
brew install parallel
StephenBoesch

2

使用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

1
是! 阿努巴瓦,你真是个天才!
斯科特

1
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

0

涉及更多一点,但可能有用:

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

0

但是,如果元组大于关联数组可以容纳的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

现在元素的数量没有限制。不找票 只是大声思考。REF1REF2


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.