shell实现group by聚合操作统计

在sql中,我们可以方便的使用group by及相应的聚合函数如sum avg count来实现分组统计需求,那当我们面对一个文本,在shell中也可以实现相应的功能吗?

在shell中,我们主要用awk来实现类似的统计需求,如下我们用例子来解析说明。 

 

数据准备

[root ~]#cat tdata.txt
apple 20 5.5
pear 10 4.5
apple 10 6.5

 

我们以空格为分割符,创建一个水果类的售价及数量记录。第一列为水果名name,第二列为数量num,第三列为单价price。

 

数据初探,输出各列的数据

在进行awk操作之前,一般待处理的文本文件行数都比较多,列数也多,而当我们指定不同的分割符时,每个字段究竟输出在第几列有可能数起来也会有点麻烦,所以此时一般先拿第一行做下各字段对应值输出,方便后续的对指定列的处理

awk的语法格式为:pattern { action },对输入数据一行行处理。只有满足只定的pattern表达式,后面的action才会进行处理,如默认的BEGIN END,还有用户自定义的各种表达式判断条件。

当未指定分割符时,默认以空白符作为分割。NR NF是awk中的内置参数,NR代表当前文件处理到第几行,NF代表每行分割后的字段数。

[root ~]#awk 'NR==1{for(i=1;i<=NF;i++){print "$"i,"=", $i}}' tdata.txt
$1 = apple
$2 = 20
$3 = 5.5

这样我就就知道了$1 $2到$NF对应的输出值,对我们后续要对指定哪列进行分析提供位置参考,当然我们也可以指定另外的分割符看下,-F参数用于指定分割符。

[root ~]#awk -F'.' 'NR==1{for(i=1;i<=NF;i++){print "$"i,"=", $i}}' tdata.txt
$1 = apple 20 5
$2 = 5

有了对数据的基本感知,我们就可以进一步来处理数据了。

 

统计水果数量之和

[root ~]#awk '{s+=$2}END{print s}' tdata.txt
40

awk中  END是在处理完所有行后才会进行的一个操作。在这里,当累加完所有行后,进行了求和输出。s未进行初始化赋值,初始默认为0

此处相当于sql:  select sum(num) from tdata

 

统计apple数量之和

[root ~]#awk '$1=="apple"{s+=$2}END{print s}' tdata.txt
30

由上面讲解的pattern {action}处理模式,我们只需要在上一次中加入pattern表达式即可: $1=="apple"

此处相当于sql: select sum(num) from tdata where name='apple'

 

统计水果的种类

[root ~]#awk '{s[$1]}END{for(i in s){print i}}' tdata.txt
apple
pear

awk是支持数组的,它提供了更加强大的功能操作,如上为创建了一个叫s的数组,数组的索引值为第一列,因为我们这里只需要用到索引值,所以只写了s[$1],因为未赋值的变量都初始默认为0,所以这里相当于s[$1]=0,而在后面的for(i in s)的循环中,i为s的索引值,所以就遍历出了所有的水果种类

此处相当于sql: select distinc name from tdata

 

统计各种水果的总价格

[root ~]#awk '{s[$1]+=$2*$3}END{for(i in s){print i,s[i]}}' tdata.txt
apple 175
pear 45

每种分类的总价格等于数量*价格。所以只需要把第二字段和第三字段相乘累加即可

此处想当于sql: select sum(num*price) from tdata group by name

 

统计各种水果的平均值

[root ~]#awk '{s[$1]+=$2*$3; t[$1]+=$2}END{for(i in s){print i,s[i]/t[i]}}' tdata.txt
apple 5.83333
pear 4.5

由于需要计算均值,故除了上面提到的求总价的数组外,还需要一个数量来存储其总数量,以便做相除,这里我们再引入一个数组t来存储总数量。在最后输出时再做相除即可得出其平均值。

此处相当于sql: select name,avg(num*price)/sum(num) from tdata group by name

 

注意这里的输出小数点位数,输出并不美观,要控制awk的输出,需要用printf函数,如下我们可以保留两位小数点。

[root ~]#awk '{s[$1]+=$2*$3; t[$1]+=$2}END{for(i in s){printf("%s %0.2f\n", i, s[i]/t[i]) }}' tdata.txt
apple 5.83
pear 4.50

awk中print与printf的一个区别是print输出默认会带换行,而printf得自己加上换行输出符。当print不符合你的输出格式要求时,就可以用printf。

 

posted @ 2020-08-16 11:39  泽锦  阅读(3312)  评论(0编辑  收藏  举报