[TOC]
# 數據轉換與提煉
> awk其中一個最為常用的作用就是格式轉換處理,通常源數據來自于某個程序產生的輸出結果信息,這些數據需要通過`awk`進行格式轉換處理然后提供給另外的程序進行操作。
> 另外一個應用是從一個比較大的數據集合當中提取出相關的數據進行處理或分析,通常提取數據過程中會進行格式化處理以及信息匯總等處理。
> 本節包含一系列相關示例來幫助大家更加熟練的編寫`Awk`腳本。
## 示例: 匯總各列數據之和
>前面我們簡單使用過`awk`的命令行求和方法,接下來我們以這個示例來編寫一個稍微復雜一些的求和示例.
功能說明: 對每列的數值進行求和,最后輸出每列的總和信息
輸入數據: M行N列的數字
輸出數據: N列數字(每列數字之和),每列用`\t`分隔符分開
舉例:
```
輸入數據:
1 2 3
2 2 2
3 3 3
輸出數據:
6 7 8
```
以下是`Awk`代碼示例:
```
# sum1 - 對每列的數值進行求和,最后輸出每列的總和信息
{
for(i = 1; i<= NF; i++)
sum[i] += $i
if( NF > max_fd)
max_fd = NF
}END{
for( i = 1; i < max_fd ; i++)
printf("%g\t", sum[i])
printf("%g\n", sum[max_fd]);
}
```
```
# sum2 過濾掉非數字列的求和
NR=1{ nf = NF;
for(i = 1; i <= nf; i++)
numcol[i] = isnum($i)
}
{ for(i = 1; i <= NF; i++)
if(numcol[i])
sum[i] += $i
}
END{
for( i = 1; i <= nf ; i++) {
if(numcol[i])
printf("%g" , sum[i])
else
printf("--")
printf(i < nf ? "\t" : "\n")
}
}
function isnum(n){ return n~/^[+-]?[0-9]+$/ }
```
思考問題:
1. 本例考慮了可能中間某個字段為空情況,這樣導致解析字段順序錯位如“1\t\t2”,本來應該是三列,由于第二列為空就被解析為兩列,考慮下設置`FS`試試怎么樣(或者`-F'\t'`)?
2. 為數據處理忽略空白行?
3. 為數字識別表達式增加更全的識別規則(如小數)?
## 示例:計算百分比和數量
> 最近得到了一批學生考試成績,我們想要通過Awk命令腳本對這些成績進行以下分析,看看在每個分數段的學生人數占比分布情況。
下面我們來看看實現方法:
```
$ cat histgram
#!/usr/bin/awk -f
# histgram
# input : [0-100]數字
# output: 分數分布圖
/^[0-9]+$/{ x[ int($1/10) ]++ ; total++}
$1>=60{ pass++}
END{
for(i = 0 ; i < 10; i++)
printf(" %2d - %2d: %3d %s\n", 10*i , 10*i+9, x[i], rep(x[i],"+"))
printf("100: %3d %s\n", x[10], rep(x[10],"+"))
printf("total: %3d ,pass: %3d, pecent: %4.02f%\n", total , pass , pass/total*100);
}
function rep(n, s, t){
while( n-- > 0)
t = t s
return t
}
$ head data
70
85
61
90
51
26
36
4
22
23
#執行方法:
$ ./histgram data
0 - 9: 17 +++++++++++++++++
10 - 19: 15 +++++++++++++++
20 - 29: 23 +++++++++++++++++++++++
30 - 39: 18 ++++++++++++++++++
40 - 49: 23 +++++++++++++++++++++++
50 - 59: 18 ++++++++++++++++++
60 - 69: 21 +++++++++++++++++++++
70 - 79: 24 ++++++++++++++++++++++++
80 - 89: 14 ++++++++++++++
90 - 99: 20 ++++++++++++++++++++
100: 12 ++++++++++++
total: 205 ,pass: 91, pecent: 44.39%
```
通過這樣的分析,我們能夠直觀的看到分數排布情況和及各的人數占比。
思考問題:
1. 如何避免因為輸入數據量過大引起的輸出"+"號過長問題?
## 示例: 隨機數的生成方法
> 為了配合上面的示例,我們編寫個隨機數生成腳本命令,這樣我們可以隨即生成一些成績數了。
```
$ cat rand.awk
#!/usr/bin/awk -f
## 功能: 輸出數值數列
## 參數: r c, r c m ,
## 輸出: 輸出 r行 , c列隨機數 ,最大值為 m(默認100)
function usage(f){
printf("Usage: %s r c\n", f);
printf(" %s r c m\n", f);
printf("其中 r :行數, c :列數 , m :最大數值,默認100 \n");
}
BEGIN{
if( ARGC == 3 ){r = ARGV[1] ; c = ARGV[2] ; m = 100}
else if( ARGC == 4 ){r = ARGV[1] ; c = ARGV[2] ; m = ARGV[3] }
else {
usage(ARGV[0])
exit 1
}
"echo $RANDOM" | getline sr
srand(sr) ## 設置隨機數種子,避免生成相同序列
for( i = 0; i< r ; i ++)
{
for(j = 0 ; j < c -1; j++)
printf("%d ", rand()*m);
printf("%d\n", rand()*m);
}
}
```
思考問題:
1. 我們已經介紹過srand()函數了,你應該知道為什么使用`sr`隨即值了吧?
## 示例: 逗號表達形式數字處理方法
> 逗號數字表達形式時,我們需要將逗號去掉了才能進行數值計算,下面這個例子就是這樣的功能。
下面這個例子用來對逗號表達形式數值進行求和計算:
```
$ cat sumcomma.awk
#!/usr/bin/awk -f
# sumcomma.awk - 計算使用逗號表達數字的總和
# 例如 123,456.78
{ gsub(",",""); sum += $0 }
END{ print sum}
```
我們看到這個腳本使用gsub()函數完成的替換逗號功能,但是大家也都想到了一個隱患,就是沒有判斷逗號所在位置是否正確的問題,也就是缺少了數字格式校驗。
下面示例功能是將數字修改成逗號表達格式:
```
$ cat addcomma.awk
#!/usr/bin/awk -f
# addcomma - 按照逗號格式表達數字
# input: 數字
# output:逗號格式表達數字
# eg: rand.awk 30 1 99900000000 | ./addcomma.awk
{ printf("%-12s %20s\n", $0, addcomma($0))}
function addcomma(x, num) {
if( x < 0 )
return "-" addcomma(-x)
num = sprintf("%.2f", x) # num is dddddd.dd
while( num ~/[0-9][0-9][0-9][0-9]/)
sub(/[0-9][0-9][0-9][,.]/, ",&", num)
return num
}
```
思考問題:
1. 參考`addcomma.awk`方法給`sumcomma.awk`增加一個逗號表達格式檢驗規則?
## 示例: 固定長度數據的格式轉換(日期格式)
> 數據處理時,你會常常看到需要對某種定長格式數據進行格式轉換,例如: 將日期格式mmddyy轉換為yymmdd格式。
下面就是定長日期格式轉換的示例:
```
$ cat dateconvert.awk
#!/usr/bin/awk -f
# dateconvert.awk
# 日期格式轉換: 將第一列轉換 mmddyy ==> yymmdd
{
$1=substr($1,5,2) substr($1,1,2) substr($1,3,2)
print
}
```
日期格式轉換后,我們就可以對數據按年進行排序了,也可以提供給其他程序作為輸入數據了。
思考問題:
1. 給這個轉換功能的日期格式增加一個有效性驗證?
2. 如何實現一個將日期轉換為天數的函數功能,轉換后的結果可以用來對兩個日期進行大小比較?
## 示例: 程序交叉引用檢查工具
> Awk命令常用于提取其他程序輸出的信息,有時候輸出信息是一些同類信息行(字段分隔或substr處理足以應付), 有時候上游輸出信息是提供給人看的(非固定格式),這時Awk程序要做的是針對不同格式進行仔細的處理, 以便從不相關的信息中提取信息。
二進制可執行程序或者函數庫文件通常都是通過很多文件編譯鏈接生成的。每個文件中可以定義不同類別的功能函數,這樣既方便函數分類也方便重復使用。
Unix和Linux系統中都有`nm`命令幫助我們顯示輸出對象文件中的符號信息,下面就是`/usr/lib64/libelf.a`文件的符號信息:
```
$ nm /usr/lib64/libelf.a |head
elf_version.o:
0000000000000000 T elf_version
U _GLOBAL_OFFSET_TABLE_
U __libelf_seterrno
0000000000000000 D __libelf_version
0000000000000004 C __libelf_version_initialized
elf_hash.o:
0000000000000000 T elf_hash
```
只有一列的行是對象文件名,兩列的行為使用函數名稱,三列的行是此文件定義的函數名稱,T表示定義的是函數,U表示這個名稱是沒有定義在此文件中。
```
# nm.format - 為nm輸出信息增加文件名稱
NF==1 { file = $1 }
NF==2 { print file,$1,$2}
NF==3 { print file,$2,$3}
```
當然,這是一個簡單示例,實際上`nm`命令是可以通過參數增加文件名稱以及行號信息等等。
## 示例:格式化輸出支票信息
> 接下來我們來看一個格式化輸出信息的示例,打印費用賬單。
我們假設輸入信息有三列,格式為: `流水號\t費用合計\t付費人`
輸出格式如下:
```
12345
10月 08日,16:44:59
Pay to Jim------------------------------------------ $1020.05
the sum of one thousand twenty dollars and 5 cents exactly
```
來看看具體實現的代碼:
```
#!/usr/bin/awk -f
## prchecks.awk
# input: number \t amount \t payee
# output: 八行文本信息
BEGIN{
FS="\t"
dashes = sp45 = sprintf("%45s", " ")
gsub(/ /,"-",dashes)
"date"|getline date
split(date, d, " ")
date = d[2] " " d[3] "," d[5]
initnum()
}
NF != 3 || $2 >= 1000000 {
printf("\nline %d illegal:\n%s\n\nVOID\nVOID\n\n\n",NR,$0)
next
}
{
printf("\n")
printf("%s%s\n", sp45, $1)
printf("%s%s\n", sp45, date)
amt = sprintf("%.2f", $2)
printf("Pay to %45.45s $%s\n",$3 dashes, amt)
printf("the sum of %s\n", numtowords(amt))
printf("\n\n\n")
}
function numtowords(n, cents, dols){
cents = substr(n, length(n)-1,2) + 0
dols = substr(n,1,length(n)-3) +0
if(dols == 0)
return "zero dollars and " cents " cents exactly"
return intowords(dols) " dollars and " cents " cents exactly"
}
function intowords(n) {
n = int(n)
if(n >= 1000)
return intowords(n/1000) " thousand " intowords(n%1000)
if(n >= 100)
return intowords(n/100) " hundred " intowords(n%100)
if(n >= 10)
return tens[int(n/10)] " " intowords(n%10)
return nums[n]
}
function initnum(){
split("one two three four five six seven eight nine "\
"ten eleven twelve thirteen fourteen fifteen "\
"sixteen seventeen eighteen nineteen", nums, " ")
split("ten twenty thirty forty fifty sixty "\
"seventy eighty ninety", tens, " ")
}
```
- 目錄
- 概述
- 第一章 編寫第一個Awk命令
- 1.1 什么是Awk命令
- 1.2 第一個Awk命令
- 第二章 Awk的模式匹配
- 2.1 Awk模式語法規則
- 2.2 Awk模式規則詳解
- 第三章 Awk的動作規則
- 3.1 Awk動作匹配語法規則
- 3.2 Awk動作規則詳解
- 第四章 Awk數據處理方法
- 4.1 數據轉換和提煉
- 4.2 數據驗證
- 4.3 數據打包與拆包處理
- 4.4 多行數據處理
- 4.5 隨機數生成
- 第五章 Awk的輸出報告和腳本封裝
- 5.1 輸出報告
- 5.2 封裝查詢結果和報告
- 第六章 Awk實現排序算法
- 6.1 插入排序算法實現
- 6.2 快速排序算法實現
- 6.3 堆排序算法實現
- 6.4 拓撲排序算法實現
- 總結