一道统计数值的awk小题目

忘记是某天了在linux系统运维qq群(198173206)看到大家在讨论一个awk统计小问题,挺感兴趣,就把这道题目记录下来,自己也写了下解决方法,就当练练手了。

原始数据,请保存为sms.txt

sms 联通  105   97     5.25    2.813
sms 移动  572   544    28.6    20.4
sms 移动  7845  6915   392.25  259.3125
sms 联通  256   208    12.8    6.032
sms 移动  7613  6810   380.65  255.375
sms 联通  480   404    24      11.716
sms 移动  521   466    26.05   17.475
sms 联通  614   538    30.7    15.602
sms 联通  1933  1495   96.65   43.355
sms 联通  885   740    44.25   21.46

问,如上数据输出成如下格式:

移动    sum($3)   sum($4)   sum($5)  sum($6)
联通    sum($3)   sum($4)   sum($5)  sum($6)
电信    sum($3)   sum($4)   sum($5)  sum($6)

1.某网友(时间长了,未记录名字,抱歉)给出了解决方法,这也是比较容易想出来的方法,简单易行,就是用第二列作为数组下标,然后第三列的数相加放到一个数组a,第四列的数相加放到一个数组b,第五列的数相加放到一个数组c,第六列的数相加放到一个数组d,最后输出结果,如下:

awk '{a[$2]+=$3;b[$2]+=$4;c[$2]+=$5;d[$2]+=$6
}END{
for (i in a)print i,a[i],b[i],c[i],d[i]}' sms.txt
联通 4273 3482 213.65 100.978
移动 16551 14735 827.55 552.562

2.有网友问到能不能用一维数组实现,于是@魔都-网吧网管给出如下方法:

awk 'BEGIN{Start=3}{
    for(i=Start;i<=NF;i++){
        a[$2","i]+=$i
        }
    c[$2]++
}END{
    for(j in c){
        printf j OFS
        for(i=Start;i<=NF;i++){
            printf a[j","i] OFS
        }
        print ""
    }
}' sms.txt

把这段代码拆开看下就明白了:

数组a是以第二列“运营商”+第n(n>=3)列为数组下标,最后的统计结果为值,如下:

awk 'BEGIN{Start=3}{for(i=Start;i<=NF;i++){a[$2","i]+=$i}c[$2]++
}END{
for(i in a) print i,a[i]}' sms.txt
移动,3 16551
移动,4 14735
移动,5 827.55
移动,6 552.562
联通,3 4273
联通,4 3482
联通,5 213.65
联通,6 100.978

数组c记录的是第二列“运营商”的行数,主要作用是输出结果时取出运营商(“联通”或者“移动”)这两个字段:

awk 'BEGIN{Start=3}{for(i=Start;i<=NF;i++){a[$2","i]+=$i}c[$2]++
}END{
for(j in c) print j,c[j]}' sms.txt
联通 6
移动 4

最后,就是END块中根据数组a和数组c的结果,做些格式化的工作,输出相应结果:

awk 'BEGIN{Start=3}{
for(i=Start;i<=NF;i++){a[$2","i]+=$i}c[$2]++
}END{
for(j in c){printf j OFS;for(i=Start;i<=NF;i++){printf a[j","i] OFS}print ""}}' sms.txt
联通 4273 3482 213.65 100.978
移动 16551 14735 827.55 552.562

3.我看到题目后,第一反应是应该用二维数组来做,如下可以看到程序跟@魔都-网吧网管的就少了对””,解决思路就变了,我想去掉数组c,第二列运营商“联通”和“移动”应该是可以直接从二维数组a中读取出来的,然后再for循环一下输出结果就行了,结果想了半天,水平不够,没有想出来如何简单的从二维数组a中取出第二列“联通”和“移动”这两个字段,这个先挖坑,等以后想出来再填了,程序如下 :

awk 'BEGIN{Start=3}{
for(i=Start;i<=NF;i++){a[$2,i]+=$i}c[$2]++
}END{
for(j in c){printf j OFS;for(i=Start;i<=NF;i++){printf a[j,i] OFS}print ""}}' sms.txt
联通 4273 3482 213.65 100.978
移动 16551 14735 827.55 552.562

与@魔都-网吧网管的程序比起来只是数组a变成了二维数组,如下:

awk 'BEGIN{Start=3}{
for(i=Start;i<=NF;i++){a[$2,i]+=$i}c[$2]++
}END{for(i in a) print i,a[i]}' sms.txt
移动3 16551
移动4 14735
移动5 827.55
移动6 552.562
联通3 4273
联通4 3482
联通5 213.65
联通6 100.978

4.正好最近在看Perl语言,发现与Python比起来,其实Perl更适合运维用,处理文本太方便了如下sms.pl代码(刚学,写的不好,见笑了。。):

#!/usr/bin/perl

use utf8;
use strict;
use warnings;
use Data::Dumper;

my %sp;
open FH,'<','sms.txt' or die "Can't open sms.txt: $!";
while (<FH>) {
    my @line = split ' ',$_;
    foreach (0..3) {
        $sp{$line[1]}[$_] += $line[$_ + 2]
    }
}
foreach my $k (keys %sp) {
    print "$k $sp{$k}[0] $sp{$k}[1] $sp{$k}[2] $sp{$k}[3]\n";
}
print Dumper(\%sp);

执行下,看下哈希sp的内容,就明白意思了:

移动 16551 14735 827.55 552.5625
联通 4273 3482 213.65 100.978
$VAR1 = {
          '移动' => [
                        16551,
                        14735,
                        '827.55',
                        '552.5625'
                      ],
          '联通' => [
                        4273,
                        3482,
                        '213.65',
                        '100.978'
                      ]
        };

参考资料:

linux awk数组操作详细介绍:http://www.cnblogs.com/chengmo/archive/2010/10/08/1846190.html


需要填的坑,如何方便地取出二维数组的第一维下标?

如下述代码中,如何简单的取出“移动”和“联通”这两个数组下标?

awk '{for(i=3;i<=NF;i++) a[$2,i]+=$i}END{for (i in a) print i,a[i]}' sms.txt
移动3 16551
移动4 14735
移动5 827.55
移动6 552.562
联通3 4273
联通4 3482
联通5 213.65
联通6 100.978