摘要:
你会学到一种原创的同时循环两个列表的方法。类似于python或者haskell的zip函数,非常简洁直观,效果如下:
$ paste seq 1 5) seq 129 133) | while read host ip; do echo vm$host: 172.16.116.$ip; donevm1: 172.16.116.129vm2: 172.16.116.130vm3: 172.16.116.131vm4: 172.16.116.132vm5: 172.16.116.133
详情:
在实际应用中,经常需要我们输入对应的两个列表,比如主机名和ip:
vm110 172.18.11.129vm111 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 clist2=1 2 3array1=($list1)array2=($list2)count=${#array1[@]}for i in `seq 1 $count`do echo ${array1[$i-1]} ${array2[$i-1]}done
谁也不想写类似${#array1[@]}这样的复杂表达,因为我们不是在编程,而是在输入一条命令。
2、有的使用了各种正则表达式命令,我一眼看不出来什么意思,没人会为了循环两个列表,去专门写一个脚本文件:
#!/bin/shlist1=1 2 3list2=a b cwhile [ -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 $head2done
还有其他几种,有兴趣的可以去看看,http://stackoverflow.com/questions/546817/iterating-over-two-lists-in-parallel-in-bin-sh。
但是有一种方法提醒了我:
list1=aaa1 aaa2 aaa3list2=bbb1 bbb2 bbb3tmpfile1=$( mktemp /tmp/list.xxxxxxxxxx ) || exit 1tmpfile2=$( mktemp /tmp/list.xxxxxxxxxx ) || exit 1echo $list1 | tr ' ' '\n' > $tmpfile1echo $list2 | tr ' ' '\n' > $tmpfile2paste $tmpfile1 $tmpfile2rm --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个及以上同等长度的列表,而且非常直观。就是开头提到的方法。