在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。