【问题标题】:Loop over tuples in bash?在bash中循环元组?
【发布时间】:2012-03-31 13:40:38
【问题描述】:

是否可以在 bash 中循环元组?

例如,如果以下方法可行,那就太好了:

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

是否有一种解决方法可以让我循环遍历元组?

【问题讨论】:

  • 来自python背景,这确实是一个非常有用的问题!
  • 四年后看看这个,我想知道是否还有更好的方法来做到这一点。天哪。
  • 差不多 8 年后,我还想知道是否还有更好的方法可以做到这一点。但是这个 2018 年的答案对我来说看起来不错:stackoverflow.com/a/52228219/463994

标签: bash for-loop


【解决方案1】:
$ 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

通过this blog

编辑:@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

【讨论】:

  • 很好——只是想指出IFS 如果在命令行上运行,应该保存并重置为其原始值。此外,新的IFS 可以在循环运行之前设置一次,而不是每次迭代。
  • 如果 $i 中的任何一个以连字符开头,set -- $i 会更安全
  • 不保存IFS,只为set命令设置:for i in c,3 e,5; do IFS="," set -- $i; echo $1 and $2; done。请编辑您的答案:如果所有读者都只选择列出的解决方案之一,那么阅读完整的开发历史是没有意义的。感谢这个很酷的技巧!
  • 如果我声明 tuples="a,1 b,2 c,3" 并把 IFS=',' 放在编辑版本中,而不是 c,3 e,5 使用 $tuples 它根本打印不好。但是,如果我将 IFS=',' 放在 for 循环中的 do 关键字之后,则在使用 $tuples 以及其他值时效果很好。只是觉得值得一说。
  • @Simonlbc 这是因为 for 循环使用 IFS 来拆分迭代。即如果你循环一个数组,如arr=("c,3" "e,5") 并将IFS 放在for 循环之前,$i 的值将只是ce,它将分开35 所以set 无法正确解析,因为 $i 没有任何东西可以解析。这意味着如果要迭代的值未内联,则应将 IFS 放在循环内,并且外部值应尊重要迭代的变量的预期分隔符。在$tuples 的情况下,它应该是简单的IFS=,它是默认的并且会根据空格进行拆分。
【解决方案2】:

This bash 样式指南说明了如何使用read 在分隔符处拆分字符串并将它们分配给各个变量。因此,使用该技术,您可以解析字符串并使用单行分配变量,如下循环中的行:

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

【讨论】:

    【解决方案3】:

    根据@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
    

    【讨论】:

    • 在我看来,这种方法比公认的和最受欢迎的方法简单得多。与@Eduardo Ivanec 的建议相反,有什么理由不这样做吗?
    • @spurra 这个答案是 6 年又 ½ 更新,并基于它。应得的信用。
    • @Diego 我知道这一点。它明确写在答案中。我在问是否有任何理由不使用这种方法而不是接受的答案。
    • @spurra 如果默认分隔符(空格、制表符或换行符)由于某种原因对您不起作用(bash.cyberciti.biz/guide/$IFS),您想使用 Eduardo 的答案
    【解决方案4】:

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

    【讨论】:

    • 仅供参考,这对我在带有GNU bash, version 4.4.23(1)-release-(x86_64-apple-darwin17.5.0) 的 Mac 上不起作用,它是通过 brew 安装的,所以 YMMV。
    • 它确实在 Docker 容器中的 Ubuntu 14.04 上的 GNU bash, version 4.3.11(1)-release-(x86_64-pc-linux-gnu) 上工作。
    • 似乎旧版本的 bash 或不支持此功能的版本基于索引?其中键是数字而不是字符串。 tldp.org/LDP/abs/html/declareref.html,而不是 -A 我们有 -a
    • 看来迭代键的关联数组顺序是随机的,所以要小心...stackoverflow.com/questions/29161323/…
    • @rogerdpack 谢谢!我已经删除了我的答案中处理非确定性迭代顺序的部分,当前的解决方案应该始终按照你的定义进行迭代。
    【解决方案5】:
    c=('a' 'c')
    n=(3    4 )
    
    for i in $(seq 0 $((${#c[*]}-1)))
    do
        echo ${c[i]} ${n[i]}
    done
    

    有时可能更方便。

    解释 ugly 部分,如 cmets 中所述:

    seq 0 2 生成数字序列 0 1 2。$(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)))for i in $(seq 0 $((2-1)))for i in $(seq 0 1)for i in 0 1。因为数组中最后一个元素的索引是数组的长度 - 1。

    【讨论】:

    • 你应该这样做for i in $(seq 0 $(($#c[*]}-1))); do [...]
    • 哇,这赢得了“我今天见过的最丑的一群任意字符”奖。有人愿意解释一下这个可憎的东西到底是做什么的吗?我在井号上迷路了……
    • @koniiiik:已添加说明。
    • "Neu-Perl: The Revenge" 我认为在 perl 中它实际上会更清晰。 :-)
    【解决方案6】:
    $ echo 'c,3;e,5;' | while IFS=',' read -d';' i j; do echo "$i and $j"; done
    c and 3
    e and 5
    

    【讨论】:

      【解决方案7】:

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

      【讨论】:

      • 不喜欢这个?制作精良的克服惯性安装gnu parallel
      • brew install parallel
      【解决方案8】:

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

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

      【讨论】:

        【解决方案9】:
        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
        

        【讨论】:

          【解决方案10】:

          在进程替换中使用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
          

          【讨论】:

            【解决方案11】:

            涉及更多,但可能有用:

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

            【讨论】:

              【解决方案12】:

              如果我的元组定义更复杂,我更喜欢将它们放在heredoc中:

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

              这结合了looping over lines of a heredocsplitting the lines at some desired separating character

              【讨论】:

                猜你喜欢
                • 2019-04-02
                • 2012-04-16
                • 2012-05-24
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2014-05-28
                • 2015-09-01
                • 2018-03-01
                相关资源
                最近更新 更多