公司的一个同事在写shell 的时候,提了这么一个需求,sar的输出中,将idle列输出 ,而且要求适用不同的平台 。在linux下通过awk打印$NF列就是ilde列,假设在其他平台下,如freebsd下idle列是$(NF-1)列,hp-unix下是$1列 (只是假设,请勿直接对号入座),该shell 该怎么写?

思路:先是想到的通过awk找到匹配idle字符串的列号,再打印该列即可。不过琢磨了一下午没实现,后来又想到通过将列转换为行,再grep idle行即可。后种思路通过测试实现了。

一、python实现(列转行思路)

这个是最快写出的一种方法,原理是列转行,再判断某个关键字在不在行里(即转换前的列里),在就打印出来。

测试文件如下:

1[root@361way ~]# cat /tmp/test.file
2CPU     %user     %nice   %system   %iowait    %steal     %idle
3all      0.50      0.00      0.50      0.00      0.00     98.99
4all      0.50      0.00      0.00      0.50      0.00     99.00
5all      0.50      0.00      0.25      0.25      0.00     99.00

python代码如下:

1with open('test.file') as f:
2  lis = [x.split() for x in f]
3for x in zip(*lis):
4    if '%idle' in x:
5        print x

先是将各行取出,并通过空格切分为各个元素,lis的结果为[(a,b) ,(c,d)] 这样的格式。再通过zip组合为列。最后就是判断了。很简单,对吧!

二、awk+grep实现(列转行)

awk实现行列转换的代码如下:

1# awk '{for(i=1;i<=NF;i++)a[NR,i]=$i}END{for(j=1;j<=NF;j++)for(k=1;k<=NR;k++)printf k==NR?a[k,j] RS:a[k,j] FS}' test.file

看起来是不是很蛋疼 ,连三元运算符都用上了。简化下:

1[root@361way tmp]# awk '{for(i=0;++i<=NF;)a[i]=a[i]?a[i] FS $i:$i}END{for(i=0;i++<NF;)print a[i]}'  test.file
2CPU all all all
3%user 0.50 0.50 0.50
4%nice 0.00 0.00 0.00
5%system 0.50 0.00 0.25
6%iowait 0.00 0.50 0.25
7%steal 0.00 0.00 0.00
8%idle 98.99 99.00 99.00

最后实现的代码为:

1awk '{for(i=0;++i<=NF;)a[i]=a[i]?a[i] FS $i:$i}END{for(i=0;i++<NF;)print a[i]}'  test.file |grep idle

貌似看起来还不错,不过感觉还是不满意,还是想通过直接列匹配实现。

三、直接列匹配

把问题在群里抛出后,果然有大牛搞出了直接awk匹配idle字符串的列号,再打印该列的方法。具体如下:

方法1:for循环

1[root@361way tmp]# awk 'NR==1{for(i=1;i<=NF;i++)if($i~/idle/)n=i}NR>1{print $n}' test.file
298.99
399.00
499.00

方法2:when循环 , array取

1[root@361way tmp]# awk 'NR==1{while(n++<NF)a[$n]=n} {if( NR>1) print $a["%idle"]}' test.file
298.99
399.00
499.00

在数据最比较大时,这里的方法1相对更高效。

方法3:python实现

python在简单文本流处理上体现不出优势,我这里写了一个,代码感觉相当长,但便于理解---原理还是逐行取某列,代码如下:

 1import linecache
 2theline = linecache.getline(r'/tmp/sar.txt', 1)
 3nums = range(len(theline.split()))
 4titles = theline.split()
 5for num in nums:
 6     if 'idle' in titles[num]:
 7         print num
 8         with open('/tmp/sar.txt') as f:
 9              for row in f:
10                  print row.split()[num]

最后:这里再推荐一个perl 版的工具csvgrep ,具体可以参该工具示例中” csvgrep '@header $NR == 1; ${Name}' contacts.csv "的用法 。实际应用中也有将某行转为列的需求,或者将某列改为行输出,这样便于取值。这里再给一个方便将某列转为行的代码,如将最后一列转为行:

1awk '{printf "%s", $NF}' file