好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

Bash实用技巧:同时循环两个列表

摘要: 你会学到一种原创的同时循环两个列表的方法。类似于Python或者Haskell的zip函数,非常简洁直观,效果如下: $ paste ( seq 1 5 ) ( seq 129 133 ) | while read host ip; do echo " vm$host: 172.16.116.$ip " ; done vm1: 172.16 . 116.129 vm2: 172

摘要:

你会学到一种原创的同时循环两个列表的方法。类似于Python或者Haskell的zip函数,非常简洁直观,效果如下:

$ paste  seq   1   5 )  seq   129   133 ) |  while  read host ip;  do   echo   "  vm$host: 172.16.116.$ip  " ;  done  

vm1:   172.16 . 116.129  
vm2:   172.16 . 116.130  
vm3:   172.16 . 116.131  
vm4:   172.16 . 116.132  
vm5:   172.16 . 116.133  

详情:

在实际应用中,经常需要我们输入对应的两个列表,比如主机名和IP:

vm110  172.18 . 11.129  

vm111   172.18 . 11.130  

...  

如果有很多的话,使用awk处理一个临时文件,然后使用while read来循环是不错的(例如从Excel里面拷贝成文本文件,然后用awk提取相应的列到一个文件):

 awk   '  {print $1 $3}  '  orig.txt |  while  read host ip;  do   echo  $host : $ip;  done   

但是,有没有能直接在命令行上生成这些列表并循环的方法呢?因为我更喜欢用for i in vm{110..120}; do echo $i; done这种方式来循环列表,但是这种方式只支持一个列表,怎么找到对应的另一个列表呢?

直接google,就会发现没有什么好的方法(以下均来自StackOverflow):

1、有的直接使用bash的数组甚至hash表,都是较新的版本才有,然后使用数字index来循环。这种方法一点也不直观:

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  

谁也不想写类似${#array1[@]}这样的复杂表达,因为我们不是在编程,而是在输入一条命令。

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  

还有其他几种,有兴趣的可以去看看,http://stackoverflow.com/questions/546817/iterating-over-two-lists-in-parallel-in-bin-sh。

但是有一种方法提醒了我:

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 

这种方法创建了两个临时文件,好像还不如前面的方法,但是在我看来,这很有启发性:他使用了paste来结合两个列表,这是linux下原生的合并列表命令,相当于其他语言的zip。

另外,临时文件也可以避免,因此我想出了以下的方法(并不推荐):

paste  echo  vm{ 1 .. 5 } |  tr   '   '   '  \n  ' )  echo   172.16 . 116 .{ 129 .. 133 } |  tr   '   '   '  \n  ' ) |  while  read host ip;  do   echo  $host: $ip;  done  

其中vm{1..5}会产生[vm1 vm2 vm3 vm4 vm5],以空格分隔,而paste是把两个列文件合并成一个,所以必须把空格替换成换行,这就是tr做的事。明显使用tr很不好,增加了命令的复杂度。

另外 输出当作一个文件的意思。

于是我想到了seq,好像可以指定分隔符,一查文档,居然默认就是换行,于是命令得以大幅简化:

paste  seq   1   5 )  seq   129   133 ) |  while  read host ip;  do   echo   "  vm$host: 172.16.116.$ip  " ;  done  

这个命令可以循环2个及以上同等长度的列表,而且非常直观。就是开头提到的方法。

查看更多关于Bash实用技巧:同时循环两个列表的详细内容...

  阅读:38次