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

WBOY
发布: 2016-06-07 15:10:47
原创
1409 人浏览过

摘要: 你会学到一种原创的同时循环两个列表的方法。类似于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 <span>1</span> <span>5</span>) seq <span>129</span> <span>133</span>) | <span>while</span> read host ip; <span>do</span> <span>echo</span> <span>"</span><span>vm$host: 172.16.116.$ip</span><span>"</span>; <span>done</span><span>

vm1: </span><span>172.16</span>.<span>116.129</span><span>
vm2: </span><span>172.16</span>.<span>116.130</span><span>
vm3: </span><span>172.16</span>.<span>116.131</span><span>
vm4: </span><span>172.16</span>.<span>116.132</span><span>
vm5: </span><span>172.16</span>.<span>116.133</span>
登录后复制

详情:

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

vm110 <span>172.18</span>.<span>11.129</span><span>

vm111 </span><span>172.18</span>.<span>11.130</span><span>

...</span>
登录后复制

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

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

<p>但是,有没有能直接在命令行上生成这些列表并循环的方法呢?因为我更喜欢用for i in vm{110..120}; do echo $i; done这种方式来循环列表,但是这种方式只支持一个列表,怎么找到对应的另一个列表呢?</p>
<p>直接google,就会发现没有什么好的方法(以下均来自StackOverflow):</p>
<p>1、有的直接使用bash的数组甚至hash表,都是较新的版本才有,然后使用数字index来循环。这种方法一点也不直观:</p>
<p>
</p><pre class="brush:php;toolbar:false">list1=<span>"</span><span>a b c</span><span>"</span><span>
list2</span>=<span>"</span><span>1 2 3</span><span>"</span><span>
array1</span>=<span>($list1)
array2</span>=<span>($list2)

count</span>=<span>${#array1[@]}
</span><span>for</span> i <span>in</span> `<span>seq</span> <span>1</span><span> $count`
</span><span>do</span>
    <span>echo</span> ${array1[$i-<span>1</span>]} ${array2[$i-<span>1</span><span>]}
</span><span>done</span>
登录后复制

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

2、有的使用了各种正则表达式命令,我一眼看不出来什么意思,没人会为了循环两个列表,去专门写一个脚本文件:

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

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

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

list1=<span>"</span><span>aaa1 aaa2 aaa3</span><span>"</span><span>
list2</span>=<span>"</span><span>bbb1 bbb2 bbb3</span><span>"</span><span>

tmpfile1</span>=$( <span>mktemp</span> /tmp/list.XXXXXXXXXX ) || exit <span>1</span><span>
tmpfile2</span>=$( <span>mktemp</span> /tmp/list.XXXXXXXXXX ) || exit <span>1</span>

<span>echo</span> $list1 | <span>tr</span> <span>'</span> <span>'</span> <span>'</span><span>\n</span><span>'</span>  ><span> $tmpfile1
</span><span>echo</span> $list2 | <span>tr</span> <span>'</span> <span>'</span> <span>'</span><span>\n</span><span>'</span>  ><span> $tmpfile2

paste  $tmpfile1  $tmpfile2

</span><span>rm</span> --force $tmpfile1  $tmpfile2
登录后复制

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

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

paste echo vm{<span>1</span>..<span>5</span>} | <span>tr</span> <span>'</span> <span>'</span> <span>'</span><span>\n</span><span>'</span>) echo <span>172.16</span>.<span>116</span>.{<span>129</span>..<span>133</span>} | <span>tr</span> <span>'</span> <span>'</span> <span>'</span><span>\n</span><span>'</span>) | <span>while</span> read host ip; <span>do</span> <span>echo</span> $host: $ip; <span>done</span>
登录后复制

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

另外

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

paste seq <span>1</span> <span>5</span>) seq <span>129</span> <span>133</span>) | <span>while</span> read host ip; <span>do</span> <span>echo</span> <span>"</span><span>vm$host: 172.16.116.$ip</span><span>"</span>; <span>done</span><span><br></span>
登录后复制

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

来源:php.cn
本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板