【问题标题】:Iterating over two lists in parallel in /bin/sh在 /bin/sh 中并行迭代两个列表
【发布时间】:2010-10-07 12:06:15
【问题描述】:

我有两个长度相等的列表,各个项目中没有空格:

list1="a b c"
list2="1 2 3"

我想并行迭代这两个列表,将 a 与 1 配对,b 与 2 配对,等等:

a 1
b 2
c 3

我正在尝试支持现代便携式 Bourne shell,因此 Bash/ksh 数组不可用。在紧要关头使用 awk 是可以接受的,但如果可能的话,我宁愿将其保留在纯 sh 中。

感谢您提供的任何指点!

【问题讨论】:

    标签: shell sh


    【解决方案1】:

    可能不便携(看看所有那些 bash-isms!),但它很容易阅读,其他人可能会觉得它很有用......

    list1="a b c"
    list2="1 2 3"
    array1=($list1)
    array2=($list2)
    
    count=${#array1[@]}
    for i in `seq 1 $count`
    do
        echo ${array1[$i-1]} ${array2[$i-1]}
    done
    

    【讨论】:

      【解决方案2】:

      这有点老套,但确实可以:

      #!/bin/sh
      list1="1 2 3"
      list2="a b c"
      while [ -n "$list1" ]
      do
          head1=`echo "$list1" | cut -d ' ' -f 1`
          list1=`echo "$list1" | sed 's/[^ ]* *\(.*\)$/\1/'`
          head2=`echo "$list2" | cut -d ' ' -f 1`
          list2=`echo "$list2" | sed 's/[^ ]* *\(.*\)$/\1/'`
          echo $head1 $head2
      done
      

      【讨论】:

      • FWIW 这在 Solaris 服务器上的 /bin/sh 下不起作用。它陷入无限循环,重复“1 a”
      • 哦,好吧,提问者已经接受了,所以我想这对他有用。我没有 Solaris 服务器。如果您添加带有更正的评论(它将是 sed rexgep 中的内容),我会添加它,但在那之前我会保留答案。
      • 评论接受的答案,它在 linux 或 Solaris 中对我不起作用,问题是 sed 正则表达式中的 \S 字符类快捷方式。我用 [^ ] 替换它并且它起作用了
      • 如果有人有适用于 Solaris 的版​​本,我很乐意更改接受的答案。
      • 您真的应该为此考虑 awk,请参阅我刚刚发布的解决方案。
      【解决方案3】:

      这应该是一个相当干净的解决方案,但是除非您使用bash 的进程替换,否则它需要使用临时文件。我不知道这比在每次迭代中调用 cutsed 好还是坏。

      #!/bin/sh
      
      list1="1 2 3"
      list2="a b c"
      echo $list1 | sed 's/ /\n/g' > /tmp/a.$$
      echo $list2 | sed 's/ /\n/g' > /tmp/b.$$
      
      paste /tmp/a.$$ /tmp/b.$$ | while read item1 item2; do
          echo $item1 - $item2
      done
      
      rm /tmp/a.$$
      rm /tmp/b.$$
      

      【讨论】:

        【解决方案4】:

        这应该是可移植的,并且也适用于两个以上的列表:

        #!/bin/sh
        x="1 2 3 4 5"
        y="a b c d e"
        z="A B C D E"
        
        while
            read current_x x <<EOF
        $x
        EOF
        
            read current_y y <<EOF
        $y
        EOF
        
            read current_z z <<EOF
        $z
        EOF
        
            [ -n "$current_x" ]
        do
            echo "x=$current_x y=$current_y z=$current_z"
        done
        

        使用位置参数也可以。请注意,列表元素不能以“-”开头。否则“设置”会失败。

        #!/bin/sh
        
        x="1 2 3 4 5"
        y="a b c d e"
        z="A B C D E" 
        
        while
            [ -n "$x" ]
        do
            set $x
            current_x=$1
            shift
            x="$*"
        
            set $y
            current_y=$1
            shift
            y="$*"
        
            set $z
            current_z=$1
            shift
            z="$*"
        
            echo "x=$current_x y=$current_y z=$current_z"
        done
        

        【讨论】:

        • 要提供一个好的答案,最好提供解决方案的某种解释,而不仅仅是发布代码。
        【解决方案5】:

        没关系,看到“BOURNE”并想到“BOURNE AGAIN”。将其留在这里是因为它可能对某人有用,但显然不是问题的答案,抱歉!

        --

        这有一些缺点(它不能优雅地处理不同大小的列表),但它适用于您给出的示例:

        #!/bin/bash
        
        list1="a b c"
        list2="1 2 3"
        
        c=0
        for i in $list1
        do
          l1[$c]=$i
          c=$(($c+1))
        done
        
        c=0
        for i in $list2
        do
          echo ${l1[$c]} $i
          c=$(($c+1))
        done
        

        还有更优雅的方式使用 awk 和 cut 等常见的 unix 工具,但以上是应要求的纯 bash 实现

        评论接受的答案,它在 linux 或 Solaris 中对我不起作用,问题是 sed 正则表达式中的 \S 字符类快捷方式。我用 [^ ] 替换它并且它起作用了:

        #!/bin/sh
        list1="1 2 3"
        list2="a b c"
        while [ -n "$list1" ]
        do
            head1=`echo "$list1" | cut -d ' ' -f 1`
            list1=`echo "$list1" | sed 's/[^ ]* *\(.*\)$/\1/'`
            head2=`echo "$list2" | cut -d ' ' -f 1`
            list2=`echo "$list2" | sed 's/[^ ]* *\(.*\)$/\1/'`
            echo $head1 $head2
        done
        

        【讨论】:

        • 接受的答案是这篇文章中的第二个,基于卓越的便携性。
        【解决方案6】:

        不使用数组的解决方案:

        list1="aaa1 aaa2 aaa3"
        list2="bbb1 bbb2 bbb3"
        
        tmpfile1=$( mktemp /tmp/list.XXXXXXXXXX ) || exit 1
        tmpfile2=$( mktemp /tmp/list.XXXXXXXXXX ) || exit 1
        
        echo $list1 | tr ' ' '\n'  > $tmpfile1
        echo $list2 | tr ' ' '\n'  > $tmpfile2
        
        paste  $tmpfile1  $tmpfile2
        
        rm --force $tmpfile1  $tmpfile2
        

        【讨论】:

          【解决方案7】:

          当第一个解决方案开始出现在这里时,我一直在研究基于 sed 的答案。但经过进一步调查,结果发现列表中的项目是用换行符而不是空格分隔的,这让我可以采用基于头尾的解决方案:

          original_revs="$(cd original && git rev-parse --all)" &&
          working_revs="$(cd working && git rev-parse --all)" &&
          while test -n "$original_revs"; do
              original_commit="$(echo "$original_revs" | head -n 1)" &&
              working_commit="$(echo "$working_revs" | head -n 1)" &&
              original_revs="$(echo "$original_revs" | tail -n +2)" &&
              working_revs="$(echo "$working_revs" | tail -n +2)" &&
              ...
          done
          

          我发布这个以防万一有人遇到这个问题的变体,但我会根据发布的问题授予接受的答案。

          【讨论】:

            【解决方案8】:

            作为一个班轮:

            list2="1 2 3"; 
            list1="a b c"; 
            for i in $list1; do 
                x=`expr index "$list2" " "`; 
                [ $x -eq 0 ] && j=$list2 || j=${list2:0:$x}; 
                list2=${list2:$x}; 
                echo "$i $j"; 
            done
            

            【讨论】:

              【解决方案9】:
              $ list1="1 2 3"
              $ list2="a b c"
              $ echo "$list1 $list2" | awk '{n=NF/2; for (i=1;i<=n;i++) print $i,$(n+i) }'
              1 a
              2 b
              3 c
              

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 2018-04-23
                • 2019-11-28
                • 2014-02-15
                • 2022-06-27
                • 2022-08-19
                • 1970-01-01
                • 2012-08-03
                相关资源
                最近更新 更多