摘要:
你會學到一種原創的同時循環兩個列表的方法。類似於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 < temp.txt
但是,有沒有能直接在命令行上生成這些列表並循環的方法呢?因為我更喜歡用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個及以上同等長度的列表,而且非常直觀。就是開頭提到的方法。