- 論壇徽章:
- 0
|
根據(jù)這壇里的鏈接找到的臺灣出品的幾篇好貼,感覺很不錯,又受到了一次基礎(chǔ)培訓(xùn)和鞏固復(fù)習(xí).所以收藏后也共享給大家,謝謝!
前言
Preface
有關(guān)本手冊 :
這是一本AWK學(xué)習(xí)指引, 其重點(diǎn)著重于 :
AWK 適于解決哪些問題 ?
AWK 常見的解題模式為何 ?
為使讀者快速掌握AWK解題的模式及特性, 本手冊系由一些較具
代表性的范例及其題解所構(gòu)成; 各范例由淺入深, 彼此間相互連貫,
范例中并對所使用的AWK語法及指令輔以必要的說明. 有關(guān)AWK的
指令, 函數(shù),...等條列式的說明則收錄于附錄中, 以利讀者往后撰寫
程序時查閱. 如此編排, 可讓讀者在短時間內(nèi)順暢地學(xué)會使用AWK
來解決問題. 建議讀者循著范例上機(jī)實習(xí), 以加深學(xué)習(xí)效果.
讀者宜先具備下列背景 :
[a.] UNIX 環(huán)境下的簡單操作及基本概念.
例如 : 檔案編輯, 檔案復(fù)制 及 pipe, I/O Redirection 等概念
[b.] C 語言的基本語法及流程控制指令.
(AWK 指令并不多, 且其中之大部分與 C語言中之用法一致, 本手冊
中對該類指令之語法及特性不再加以繁冗的說明, 讀者若欲深究,
可自行翻閱相關(guān)的 C 語言書籍)
Overview of AWK
Why AWK
AWK 是一種程序語言. 它具有一般程序語言常見的功能.
因AWK語言具有某些特點(diǎn), 如 : 使用直譯器(Interpreter)不需先行
編譯; 變量無型別之分(Typeless), 可使用文字當(dāng)數(shù)組的注標(biāo)
(Associative Array)...等特色. 因此, 使用AWK撰寫程序比起
使用其它語言更簡潔便利且節(jié)省時間. AWK還具有一些內(nèi)建
功能, 使得AWK擅于處理具數(shù)據(jù)列(Record), 字段(Field)型
態(tài)的資料; 此外, AWK內(nèi)建有pipe的功能, 可將處理中的數(shù)據(jù)
傳送給外部的 Shell命令加以處理, 再將Shell命令處理后的
數(shù)據(jù)傳回AWK程序, 這個特點(diǎn)也使得AWK程序很容易使用
系統(tǒng)資源.
由于AWK具有上述特色, 在問題處理的過程, 可輕易使用
AWK來撰寫一些小工具; 這些小工具并非用來解決整個大問題,
它們只個別扮演解決問題過程的某些角色, 可藉由Shell所提供的
pipe將數(shù)據(jù)按需要傳送給不同的小工具進(jìn)行處理, 以解決整個
大問題. 這種解題方式, 使得這些小工具可因不同需求而被重復(fù)
組合及使用(reuse); 也可藉此方式來先行測試大程序原型的可行性
與正確性, 將來若需要較高的執(zhí)行速度時再用C語言來改寫.
這是AWK最常被應(yīng)用之處. 若能常常如此處理問題, 讀者可以
以更高的角度來思考抽象的問題, 而不會被拘泥于細(xì)節(jié)的部份.
本手冊為AWK入門的學(xué)習(xí)指引, 其內(nèi)容將先強(qiáng)調(diào)如何撰寫AWK程序,
未列入進(jìn)一步解題方式的應(yīng)用實例, 這部分將留待UNIX進(jìn)階手冊中
再行討論.
如何取得 AWK
一般的UNIX操作系統(tǒng), 本身即附有AWK. 不同的UNIX操作系統(tǒng)
所附的AWK其版本亦不盡相同. 若讀者所使用的系統(tǒng)上未附有AWK,
可透過 anonymous ftp 到下列地方取得 :
phi.sinica.edu.tw:/pub/gnu
ftp.edu.tw:/UNIX/gnu
prep.ai.mit.edu:/pub/gnu
--------------------------------------------------------------------------------
How AWK works
為便于解釋AWK程序架構(gòu), 及有關(guān)術(shù)語(terminology), 先以一個
員工薪資檔(emp.dat ), 來加以介紹.
A125 & Jenny &100 &210
A341 & Dan &110 &215
P158 & Max &130 &209
P148 & John &125 &220
A123 & Linda & 95 &210
檔案中各字段依次為 員工ID, 姓名, 薪資率,及 實際工時. ID
中的第一碼為部門識別碼. ``A'',''P''分別表示``組裝''及``包裝''部門.
本小節(jié)著重于說明AWK程序的主要架構(gòu)及工作原理, 并對一些重要
的名詞輔以必要的解 釋. 由這部分內(nèi)容, 讀者可體會出AWK語言
的主要精神及AWK與其它語程序言的差異處. 為便于說明, 以條列
方式說明于后.
名詞定義
資料列: AWK從數(shù)據(jù)文件上讀取數(shù)據(jù)的基本單位.以上列檔案
emp.dat為例, AWK讀入的
第一筆資料列是 "A125 Jenny 100 210"
第二筆資料列是 "A341 Dan 110 215"
一般而言, 一筆數(shù)據(jù)列相當(dāng)于數(shù)據(jù)文件上的一行資料.
(參考 : 附錄 B 內(nèi)建變數(shù)``RS'' )
字段(Field) : 為資料列上被分隔開的子字符串.
以資料列``A125 Jenny 100 210''為例,
第一欄 第二欄 第三欄 第四欄 ``A125'' ``Jenny'' 100 210
一般是以空格符來分隔相鄰的字段. ( 參考 : 附錄 D 內(nèi)建
變數(shù)``FS'' )
如何執(zhí)行AWK
于UNIX的命令列上鍵入諸如下列格式的指令: ( ``$''表Shell命令
列上的提示符號)
$awk 'AWK程序' 數(shù)據(jù)文件文件名
則AWK會先編譯該程序, 然后執(zhí)行該程序來處理所指定的數(shù)據(jù)文件.
(上列方式系直接把程序?qū)懺赨NIX的命令列上)
AWK程序的主要結(jié)構(gòu) :
AWK程序中主要語法是 Pattern { Actions}, 故常見之AWK
程序其型態(tài)如下 :
Pattern1 { Actions1 }
Pattern2 { Actions2 }
......
Pattern3 { Actions3 }
Pattern 是什么 ?
AWK 可接受許多不同型態(tài)的 Pattern. 一般常使用 ``關(guān)系判斷式'
(Relational expres sion) 來當(dāng)成 Pattern.
例如 :
x > 34 是一個Pattern, 判斷變量 x 與 34 是否存在 大于 的關(guān)系.
x == y 是一個Pattern, 判斷變量 x 與變量 y 是否存在等于的關(guān)系.
上式中 x >34 , x == y 便是典型的Pattern.
AWK 提供 C 語言中常見的關(guān)系操作數(shù)(Relational Operators) 如
>, <, >=, <=, ==, !=.
此外, AWK 還提供 ~ (match) 及 !~(not match) 二個關(guān)系操作數(shù)
(注一). 其用法與涵義如下:
若 A 表一字符串, B 表一 Regular Expression
A ~ B 判斷 字符串A 中是否 包含 能合于(match)B式樣的
子字符串.
A !~ B 判斷 字符串A 中是否 未包含 能合于(match)B式樣的
子字符串.
例如 :
``banana'' ~ /an/ 整個是一個Pattern.
因為``banana''中含有可match /an/的子字符串, 故此關(guān)系式
成立(true),
整個Pattern的值也是true.
相關(guān)細(xì)節(jié)請參考 附錄 A Patterns, 附錄 E Regular Expression
[注 一 :] 有少數(shù)AWK論著, 把 ~, !~ 當(dāng)成另一類的 Operator,
并不視為一種 Relational Operator. 本手冊中將這兩個操作數(shù)
當(dāng)成一種 Relational Operator.
Actions 是什么?
Actions 是由許多AWK指令構(gòu)成. 而AWK的指令與 C 語言中的
指令十分類似.
例如 :
AWK的 I/O指令 : print, printf( ), getline..
AWK的 流程控制指令 : if(...){..} else{..}, while(...){...}...
(請參考 附錄 B --- ``Actions'' )
AWK 如何處理 Pattern { Actions } ?
AWK 會先判斷(Evaluate) 該 Pattern 之值, 若 Pattern 判斷
(Evaluate)后之值為true(或不為0的數(shù)字,或不是空的字符串), 則 AWK
將執(zhí)行該 Pattern 所對應(yīng)的 Actions.
反之, 若 Pattern 之值不為 true, 則AWK將不執(zhí)行該 Pattern
所對應(yīng)的 Actions.
例如 : 若AWK程序中有下列兩指令
50 > 23 : {print "Hello! The word!!" }
"banana" ~ /123/ { print "Good morning !" }
AWK會先判斷 50 >23 是否成立. 因為該式成立, 所以AWK將印出
"Hello! The word!!". 而另一 Pattern 為 "banana" ~/123/, 因為
"banana" 內(nèi)未含有任何子字符串可 match /123/, 該 Pattern 之值為
false, 故AWK將不會印出 "Good morning !"
AWK 如何處理{ Actions } 的語法?(缺少Pattern部分)
有時語法 Pattern { Actions }中, Pattern 部分被省略,
只剩 {Actions}.
這種情形表示 ``無條件執(zhí)行這個 Actions''.
AWK 的字段變量
AWK 所內(nèi)建的字段變量及其涵意如下 :
字段變量 涵意 $0 為一字符串, 其內(nèi)容為目前 AWK 所讀入的資料列. $1 代表 $0 上第一個字段的數(shù)據(jù). $2 代表 $0 上第二欄個位的資料. ... 其余類推
讀入數(shù)據(jù)列時, AWK如何修正(update)這些內(nèi)建的字段變量
當(dāng) AWK 從數(shù)據(jù)文件中讀取一筆數(shù)據(jù)列時, AWK 會使用內(nèi)建變量
$0 予以記錄.
每當(dāng) $0 被異動時 (例如 : 讀入新的數(shù)據(jù)列 或 自行變更 $0,...)
AWK 會立刻重新分析 $0 的字段情況, 并將 $0 上各字段的數(shù)據(jù)
用 $1, $2, ..予以記錄.
AWK的內(nèi)建變數(shù)(Built-in Variables)
AWK 提供了許多內(nèi)建變量, 使用者于程序中可使用這些變量
來取得相關(guān)信息.常見的內(nèi)建變數(shù)有 :
內(nèi)建變數(shù) 涵意 NF (Number of Fields)為一整數(shù), 其值表$0上所存在的字段數(shù)目. NR (Number of Records)為一整數(shù), 其值表AWK已讀入的資料列數(shù)目. FILENAMEAWK正在處理的數(shù)據(jù)文件文件名.
例如 : AWK 從資料文件 emp.dat 中讀入第一筆資料列
"A125 Jenny 100 210" 之后, 程序中:
$0 之值將是 "A125 Jenny 100 210"
$1 之值為 "A125" $2 之值為 "Jenny"
$3 之值為 100 $4 之值為 210
NF 之值為 4 $NF 之值為 210
NR 之值為 1 FILENAME 之值為 ``emp.dat''
AWK的工作流程 :
執(zhí)行AWK時, 它會反復(fù)進(jìn)行下列四步驟.
自動從指定的數(shù)據(jù)文件中讀取一筆數(shù)據(jù)列.
自動更新(Update)相關(guān)的內(nèi)建變量之值. 如 : NF, NR, $0...
逐次執(zhí)行程序中 所有 的 Pattern { Actions } 指令.
當(dāng)執(zhí)行完程序中所有 Pattern { Actions } 時, 若數(shù)據(jù)文件中還
有未讀取的數(shù)據(jù), 則反復(fù)執(zhí)行步驟1到步驟4.
AWK會自動重復(fù)進(jìn)行上述4個步驟, 使用者不須于程序中
撰寫這個循環(huán) (Loop).
打印檔案中指定的字段數(shù)據(jù)并加以計算
AWK 處理數(shù)據(jù)時, 它會自動從數(shù)據(jù)文件中一次讀取一筆記錄, 并會
將該數(shù)據(jù)切分成一個個的字段; 程序中可使用 $1, $2,... 直接取得
各個字段的內(nèi)容. 這個特色讓使用者易于用 AWK 撰寫 reformatter
來改變數(shù)據(jù)格式.
[ 范例 :] 以檔案 emp.dat 為例, 計算每人應(yīng)發(fā)工資并打印報表.
[ 分析 :] AWK 會自行一次讀入一列數(shù)據(jù), 故程序中僅需告訴
AWK 如何處理所讀入的數(shù)據(jù)列.
執(zhí)行如下命令 : ( $ 表UNIX命令列上的提示符號 )
awk '{ print $2, $3 * $4 }' emp.dat
執(zhí)行結(jié)果如下 :
屏幕出現(xiàn) :
Jenny 21000
Dan 23650
Max 27170
John 27500
Linda 19950
說 明 :
UNIX命令列上, 執(zhí)行AWK的語法為:
awk 'AWK程序' 欲處理的資料文件文件名.
本范例中的 程序部分 為 {print $2, $3 * $4}.
把程序置于命令列時, 程序之前后須以 ' 括住.
emp.dat 為指定給該程序處理的數(shù)據(jù)文件文件名.
本程序中使用 : Pattern { Actions } 語法.
Pattern Actions print $2, $3 * $4
Pattern 部分被省略, 表無任何限制條件. 故AWK讀入每筆資料列
后都將無條件執(zhí)行這個 Actions.
print為AWK所提供的輸出指令, 會將數(shù)據(jù)輸出到stdout(屏幕).
print 的參數(shù)間彼此以 ``{ ,}'' 隔開, 印出數(shù)據(jù)時彼此間會以空白
隔開.
(參考 附錄 D 內(nèi)建變量OFS)
將上述的 程序部分 儲存于檔案 pay1.awk 中. 執(zhí)行命令時再指定AWK程序文件 之文件名. 這是執(zhí)行AWK的另一種方式, 特別適用于程
式較大的情況, 其語法如下:
$awk -f AWK程序文件名 數(shù)據(jù)文件文件名
故執(zhí)行下列兩命令,將產(chǎn)生同樣的結(jié)果.
$awk -f pay1.awk emp.dat
$awk ' { print $2, $3 * $4 } ' emp.dat
讀者可使用``-f''參數(shù),讓AWK主程序使用其它僅含 AWK函數(shù) 的
檔案中的函數(shù)
其語法如下:
awk -f AWK主程序文件名 -f AWK函數(shù)文件名 數(shù)據(jù)文件文件名
(有關(guān) AWK 中函數(shù)之宣告與使用于 7.4 中說明)
AWK中也提供與 C 語言中類似用法的 printf() 函數(shù). 使用
該函數(shù)可進(jìn)一步控制數(shù)據(jù)的輸出格式.
編輯另一個AWK程序如下, 并取名為 pay2.awk
{ printf("\%6s Work hours: %3d Pay: %5d\", $2,\$3, $3* $4) }
執(zhí)行下列命令
$awk -f pay2.awk emp.dat
執(zhí)行結(jié)果屏幕出現(xiàn):
Jenny Work hours: 100 Pay: 21000
Dan Work hours: 110 Pay: 23650
Max Work hours: 130 Pay: 27170
John Work hours: 125 Pay: 27500
Linda Work hours: 95 Pay: 19950
選印合乎指定條件的記錄
Pattern { Action }為AWK中最主要的語法. 若某Pattern之值為真則執(zhí)行它后方的 Action. AWK中常使用``關(guān)系判斷式'' (Relational Expression)來當(dāng)成 Pattern.
AWK中除了>, <, ==, != ,...等關(guān)系操作數(shù)( Relational Operators )外,另外提供 ~(match),!~(Not Match) 二個關(guān)系操作數(shù). 利用這兩個操作數(shù), 可判斷某字符串是否包含能符合所指定 Regular Expression 的子字符串. 由于這些特性, 很容易使用AWK來撰寫需要字符串比對, 判斷的程序. [ 范例 :] 承上例,
組裝部門員工調(diào)薪5%,(組裝部門員工之ID.系以``A''開頭)
所有員工最后之薪資率若仍低于100, 則以100計.
撰寫AWK程序行印新的員工薪資率報表.
[分析 ] : 這個程序須先判斷所讀入的數(shù)據(jù)列是否合于指定條件, 再進(jìn)行某些動作.AWK中 Pattern { Actions } 的語法已涵蓋這種 `` if ( 條件 ) { 動作} ''的架構(gòu). 編寫如下之程序, 并取名 adjust1.awk
$1 ~ /^A.*/ { $3 *= 1.05 } $3<100 { $3 = 100 }
{ printf("%s %8s %d\n", $1, $2, $3)} 執(zhí)行下列命令 :
$awk -f adjust1.awk emp.dat
結(jié)果如下 : 屏幕出現(xiàn) :
A125 Jenny 105
A341 Dan 115
P158 Max 130
P148 John 125
A123 Linda 100
說 明 :
AWK的工作程序是: 從數(shù)據(jù)文件中每次讀入一筆數(shù)據(jù)列, 依序執(zhí)行完程序中所有的 Pattern{ Action }指令 Pattern Actions
Pattern Actions
$1~/^A.*/ { $3 *= 1.05 }
$3 < 100 { $3 = 100 }
{printf("%s%8s%d\n",$1,$2,$3)}
再從數(shù)據(jù)文件中讀進(jìn)下一筆記錄繼續(xù)進(jìn)行處理.
第一個 Pattern { Action }是: $1 ~ /^A.*/ { $3 *= 1.05 } $1 ~ /^A.*/ 是一個Pattern, 用來判斷該筆資料列的第一欄是否包含%以``A''開頭的子字符串. 其中 /^A.*/ 是一個Regular Expression, 用以表示任何以``A''開頭的字符串. (有關(guān) Regular Expression 之用法 參考 附錄 E ).
Actions 部分為 $3 *= 1.05 $3 *= 1.05 與 $3 = $3 * 1.05 意義相同. 運(yùn)算子``*='' 之用法則與 C 語言中一樣. 此后與 C 語言中用法相同的運(yùn)算子或語法將不予贅述.
第二個 Pattern { Actions } 是: $3 <100 {$3 = 100 } 若第三欄的數(shù)據(jù)內(nèi)容(表薪資率)小于100, 則調(diào)整為100.
第三個 Pattern { Actions } 是: {printf("%s %-8s %d\n",$1, $2, $3 )} 省略了Pattern(無條件執(zhí)行Actions), 故所有數(shù)據(jù)列調(diào)整后的數(shù)據(jù)都將被印出.
AWK 中數(shù)組的特色
AWK程序中允許使用字符串當(dāng)做數(shù)組的注標(biāo)(index). 利用
這個特色十分有助于資料統(tǒng)計工作.(使用字符串當(dāng)注標(biāo)的數(shù)組稱為
Associative Array)
首先建立一個數(shù)據(jù)文件, 并取名為 reg.dat. 此為一學(xué)生注冊的
資料文件; 第一欄為學(xué)生姓名, 其后為該生所修課程.
Mary O.S. Arch. Discrete
Steve D.S. Algorithm Arch.
Wang Discrete Graphics O.S.
Lisa Graphics A.I.
Lily Discrete Algorithm
AWK中數(shù)組的特性
使用字符串當(dāng)數(shù)組的注標(biāo)(index).
使用數(shù)組前不須宣告數(shù)組名及其大小.
例如 : 希望用數(shù)組來記錄 reg.dat 中各門課程的修課人數(shù).
這情況,有二項信息必須儲存 :
(a) 課程名稱, 如 : ``O.S.'',``Arch.''.. ,共有哪些課程事前
并不明確.
(b)各課程的修課人數(shù). 如 : 有幾個人修``O.S.''
在AWK中只要用一個數(shù)組就可同時記錄上列信息. 其方法如下 :
使用一個數(shù)組 Number[ ] :
以課程名稱當(dāng) Number[ ] 的注標(biāo).
以 Number[ ] 中不同注標(biāo)所對映的元素代表修課人數(shù).
例如 :
有2個學(xué)生修 ``O.S.'', 則以 Number[``O.S.''] = 2 表之.
若修``O.S.''的人數(shù)增加一人,
則 Number[``O.S.''] = Number[``O.S.''] + 1
或 Number["O.S."]++ .
如何取出數(shù)組中儲存的信息
以 C 語言為例, 宣告 int Arr[100]; 之后, 若想得知 Arr[ ]中所
儲存的數(shù)據(jù), 只須用一個循環(huán), 如 :
for(i=0; i<00; i++) printf("%d\n", Arr);
即可. 上式中 :
數(shù)組 Arr[ ] 的注標(biāo) : 0, 1, 2,..., 99
數(shù)組 Arr[ ] 中各注標(biāo)所對應(yīng)的值 : Arr[0], Arr[1],...Arr[99]
但 AWK 中使用數(shù)組并不須事先宣告. 以剛才使用的 Number[ ] 而言,
程序執(zhí)行前, 并不知將來有哪些課程名稱可能被當(dāng)成 Number[ ] 的
注標(biāo).
AWK 提供了一個指令, 藉由該指令A(yù)WK會自動找尋數(shù)組中使用過
的所有注標(biāo). 以 Number[ ] 為例, AWK將會找到 ``O.S.'', ``Arch.''",...
使用該指令時, 須指定所要找尋的數(shù)組, 及一個變量. AWK會使用
該的變量來記錄從數(shù)組中找到的每一個注標(biāo). 例如
for(course in Number){....}
指定用 course 來記錄 AWK 從Number[ ] 中所找到
的注標(biāo). AWK每找到一個注標(biāo)時, 就用course記錄該注標(biāo)之值且
執(zhí)行{....}中之指令. 藉由這個方式便可取出數(shù)組中儲存的信息.
(詳見下例)
范例 : 統(tǒng)計各科修課人數(shù),并印出結(jié)果.
建立如下程序,并取名為 course.awk:
{ for( i=2; i
END{for(coursein Number)
printf("\%-10s %d\n", course, Number[course] )
}
執(zhí)行下列命令 :
awk -f course.awk reg.dat
執(zhí)行結(jié)果如下 :
Discrete 3
D.S. 1
O.S. 2
Graphics 2
A.I. 1
Arch. 2
Algorithm 2
說 明 :
這程序包含二個Pattern { Actions }指令.
Pattern Actions
{for( i=2; i< NF; i++) Number[$i]++ }
END { for( course in Number) printf("\%-10s \%d\n", course, Number[course] )}
第一個Pattern { Actions }指令中省略了Pattern 部分. 故隨著
每筆數(shù)據(jù)列的讀入其Actions部分將逐次無條件被執(zhí)行.
以AWK讀入第一筆資料 `` Mary O.S. Arch. Discrete" 為例,
因為該筆數(shù)據(jù) NF = 4(有4個字段), 故該 Action 的for Loop中
i = 2,3,4.
i $i 最初 Number[$i] Number[$i]++ 之后
2 ``O.S.'' AWK default Number[``O.S'']=0 1
3 ``Arch.'' AWK default Number[``Arch'']=0 1
4 ``Discrete'' AWK default Number[``Discrete'']=0 1
第二個 Pattern { Actions }指令中
* { END}為AWK之保留字, 為{ Pattern}之一種.
* { END}成立(其值為true)的條件是 :[0.3cm]
``AWK處理完所有數(shù)據(jù), 即將離開程序時.
平常讀入資料列時, END并不成立, 故其后的Actions
并不被執(zhí)行;
唯有當(dāng)AWK讀完所有數(shù)據(jù)時, 該Actions才會被執(zhí)行 ( 注意,
不管數(shù)據(jù)列有多少筆, END僅在最后才成立, 故該Actions僅被執(zhí)行
一次.)
{ BEGIN} 與 { END} 有點(diǎn)類似, 是AWK中另一個保留的{Pattern}.
唯一不同的是 :
``以 { BEGIN 為 Pattern 的 Actions} 于程序一開始執(zhí)行時, 被執(zhí)行
一次.''
NF 為AWK的內(nèi)建變量, 用以表示AWK正處理的數(shù)據(jù)計列中,
所包含的字段個數(shù).
AWK程序中若含有以 $ 開頭的自定變量, 都將以如下方式解釋 :
以 i= 2 為例, $i = $2 表第二個字段數(shù)據(jù). ( 實際上, $ 在 AWK 中
為一操作數(shù)(Operator), 用以取得字段數(shù)據(jù).)
AWK 程序中使用 Shell 命令
AWK程序中允許呼叫Shell指令. 并提供pipe解決AWK與系統(tǒng)間
數(shù)據(jù)傳遞的問題. 所以AWK很容易使用系統(tǒng)資源. 讀者可利用這個
特色來撰寫某些適用的系統(tǒng)工具.
范例 : 寫一AWK程序來打印出線上人數(shù).
將下列程序建文件, 命名為 count.awk
BEGIN {
while ( "who" | getline ) n++
print n
}
并執(zhí)行下列命令 :
awk { -f} count.awk
執(zhí)行結(jié)果將會印出目前在線人數(shù)
說 明 :
AWK 程序并不一定要處理資料文件. 以本例而言, 僅輸入程序
檔count.awk, 未輸入任何數(shù)據(jù)文件.
BEGIN 和 END 同為AWK中之種一 Pattern. 以 BEGIN 為
Pattern之Actions,只有在AWK開始執(zhí)行程序,尚未開啟任何輸入
檔前, 被執(zhí)行一次.(注意: 只被執(zhí)行一次 )
``{ |}'' 為 AWK 中表示 pipe的符號. AWK 把 pipe之前的字符串
''who''當(dāng)成Shell上的命令, 并將該命令送往Shell執(zhí)行, 執(zhí)行的結(jié)果
(原先應(yīng)于屏幕印出者)則藉由pipe送進(jìn)AWK程序中.
getline為AWK所提供的輸入指令.
其語法如下 :
語法 由何處讀取數(shù)據(jù) 數(shù)據(jù)讀入后置于
getline var < file 所指定的 file 變量 var(var省略時,表示置于$0)
getline var pipe 變量 var(var省略時,表示置于$0)
getline var 見 注一 變量 var(var省略時,表示置于$0)
注一 : 當(dāng) Pattern 為 BEGIN 或 END 時, getline 將由 stdin 讀取數(shù)據(jù),
否則由AWK正處理的數(shù)據(jù)文件上讀取數(shù)據(jù).
getline 一次讀取一行數(shù)據(jù),
若讀取成功則return 1,
若讀取失敗則return -1,
若遇到檔案結(jié)束(EOF), 則return 0;
本程序使用 getline 所 return 的數(shù)據(jù) 來做為 while 判斷
循環(huán)停止的條件,某些AWK版本較舊,并不容許使用者改變 $0 之值.
這種版的 AWK 執(zhí)行本程序時會產(chǎn)生 Error, 讀者可于 getline 之后
置上一個變量 (如此, getline 讀進(jìn)來的數(shù)據(jù)便不會被置于 $0 ),
或直接改用gawk便可解決.
AWK 程序的應(yīng)用實例
本節(jié)將示范一個統(tǒng)計上班到達(dá)時間及遲到次數(shù)的程序.
這程序每日被執(zhí)行時將讀入二個檔案 :
員工當(dāng)日到班時間的數(shù)據(jù)文件 ( 如下列之 arr.dat )
存放員工當(dāng)月遲到累計次數(shù)的檔案.
當(dāng)程序執(zhí)行執(zhí)完畢后將更新第二個檔案的數(shù)據(jù)(遲到次數(shù)), 并打印
當(dāng)日的報表.這程序?qū)⒎殖上铝袛?shù)小節(jié)逐步完成, 其大綱如下 :
[7.1] 于到班資料文件 {arr.dat} 之前端增加一列抬頭
"ID Number Arrvial Time", 并產(chǎn)生報表輸出到檔案today_rpt1 中''
< 在AWK中如何將數(shù)據(jù)輸出到檔案 >
[7.2]將 {today\_rpt1} 上之?dāng)?shù)據(jù)按員工代號排序, 并加注執(zhí)行當(dāng)日
之日期; 產(chǎn)生檔案 today_rpt2
< AWK中如何運(yùn)用系統(tǒng)資源及AWK中Pipe之特性 >
[7.3]< 將AWK程序包含在一個shell script檔案中>
[7.4] 于 today_rpt2 每日報表上, 遲到者之前加上"*", 并加注當(dāng)日
平均到班時間; 產(chǎn)生檔案 today_rpt3
[7.5] 從檔案中讀取當(dāng)月遲到次數(shù), 并根據(jù)當(dāng)日出勤狀況更新遲到累計數(shù).
< 使用者于AWK中如何讀取檔案數(shù)據(jù) >
某公司其員工到勤時間檔如下, 取名為 {arr.dat}. 檔案中第一欄為
員工代號, 第二欄為到達(dá)時間. 本范例中, 將使用該檔案為數(shù)據(jù)文件.
1034 7:26
1025 7:27
1101 7:32
1006 7:45
1012 7:46
1028 7:49
1051 7:51
1029 7:57
1042 7:59
1008 8:01
1052 8:05
1005 8:12
將數(shù)據(jù)直接輸出到檔案
AWK中并未提供如 C 語言中之fopen() 指令, 也未有fprintf()
檔案輸出之指令. 但AWK中任何輸出函數(shù)之后皆可藉由使用與
UNIX 中類似的 I/O Redirection , 將輸出的數(shù)據(jù) Redirect 到指定
的檔案; 其符號仍為 > (輸出到一個新產(chǎn)生的檔案) 或 >> ( append
輸出的數(shù)據(jù)到檔案末端 ).
[例 :]于到班資料文件 arr.dat 之前端增加一列抬頭如下 :
"ID Number Arrival Time", 并產(chǎn)生報表輸出到檔案 today_rpt1中
建立如下檔案并取名為reformat1.awk
BEGIN { print `` ID Number Arrival Time'' > ``today_rpt1''
print ``==========================='' > ``today_rpt1''
}
{ printf(" %s %s\n", $1,$2 ) > "today_rpt1" } 執(zhí)行:
$awk -f reformat1.awk arr.dat
執(zhí)行后將產(chǎn)生檔案 today\_rpt1, 其內(nèi)容如下 :
ID Number Arrival Time
============================
1034 7:26
1025 7:27
1101 7:32
1006 7:45
1012 7:46
1028 7:49
1051 7:51
1029 7:57
1042 7:59
1008 8:01
1052 8:05
1005 8:12
說 明 :
AWK程序中, 文件名稱 today_rpt1 之前后須以" 括住, 表示
today_rpt1 為一字符串常數(shù). 若未以"括住, 則 today_rpt1 將
被AWK解釋為一個變量名稱.
在AWK中任何變量使用之前, 并不須事先宣告. 其初始值為空字符串
(Null string) 或 0.因此程序中若未以 " 將 today_rpt1 括住,
則 today_rpt1 將是一變量, 其值將是空字符串, 這會于執(zhí)行時
造成錯誤(Unix 無法幫您開啟一個以Null String為檔名的檔案).
* 因此在編輯 AWK程序時, 須格外留心. 因為若敲錯變量名稱,
AWK在編譯程序時會認(rèn)為是一新的變量, 并不會察覺. 如此
往往會造成 RuntimeError.
BEGIN 為AWK的保留字, 是 Pattern 的一種.
以 BEGIN 為 Pattern 的 Actions 于AWK程序剛被執(zhí)行尚未讀取
數(shù)據(jù)時被執(zhí)行一次, 此后便不再被執(zhí)行.
讀者或許覺得本程序中的I/O Redirection符號應(yīng)使用 `` >>''
(append)而非 `` >''.
\index{ { >} } \index{ { >>} } 本程序中若使用 ``>'' 將數(shù)據(jù)重導(dǎo)到 today_rpt1, AWK
第一次執(zhí)行該指令時會產(chǎn)生一個新檔 today_rpt1, 其后再
執(zhí)行該指令時則把數(shù)據(jù)append到today_rpt1文件末, 并非每執(zhí)行
一次就重開一個新檔. 若采用">>"其差異僅在第一次執(zhí)行該指令時, 若已存在
today_rpt1則 AWK 將直接把數(shù)據(jù)append在原檔案之末尾.
這一點(diǎn), 與UNIX中的用法不同.
AWK 中如何利用系統(tǒng)資源
AWK程序中很容易使用系統(tǒng)資源. 這包括于程序中途叫用 Shell
命令來處理程序中的部分?jǐn)?shù)據(jù); 或于呼叫 Shell 命令后將其產(chǎn)生
之結(jié)果交回 AWK 程序(不需將結(jié)果暫存于某個檔案). 這過程乃是
藉由 AWK 所提供的 pipe (雖然有些類似 Unix 中的 pipe, 但特性
有些不同),及一個從 AWK 中呼叫 Unix 的 Shell command 的語法
來達(dá)成.
[例 :] 承上題, 將數(shù)據(jù)按員工ID排序后再輸出到檔案 today_rpt2,
并于表頭附加執(zhí)行時的日期.
分 析 :
AWK 提供與 UNIX 用法近似的 pipe, 其記號亦為 ``|''. 其用法及
涵意如下 :
AWK程序中可接受下列兩語法 :
[a. 語法] AWK output 指令 | ``Shell 接受的命令''
( 如 : print $1,$2 | "sort +1n" )
[b. 語法] ``Shell 接受的命令'' |AWK input 指令
( 如 : "ls " | getline)
注 : AWK input 指令只有 getline 一個.
AWK output 指令有 print, printf() 二個.
于 a 語法中, AWK所輸出的數(shù)據(jù)將轉(zhuǎn)送往 Shell, 由 Shell 的
命令進(jìn)行處理.以上例而言, print 所印出的數(shù)據(jù)將經(jīng)由 Shell 命令
``sort +1n'' 排序后再送往屏幕(stdout).
上列AWK程序中, ``print$1, $2'' 可能反復(fù)執(zhí)行很多次, 其印出的
結(jié)果將先暫存于 pipe 中,
等到該程序結(jié)束時, 才會一并進(jìn)行 ``sort +1n''.
須注意二點(diǎn) : 不論 print \$1, \$2 被執(zhí)行幾次,
``sort +1n'' 之 執(zhí)行時間是 ``AWK程序結(jié)束時'',
``sort +1n'' 之 執(zhí)行次數(shù)是 ``一次''.
于 b 語法中, AWK將先叫用 Shell 命令. 其執(zhí)行結(jié)果將經(jīng)由
pipe 送入AWK程序以上例而言, AWK先令 Shell 執(zhí)行 ``ls'',
Shell 執(zhí)行后將結(jié)果存于 pipe, AWK指令 getline再從 pipe 中讀取
資料.
使用本語法時應(yīng)留心 : 以上例而言
AWK ``立刻''呼叫 Shell 來執(zhí)行 ``ls'', 執(zhí)行次數(shù)是一次.
getline 則可能執(zhí)行多次(若pipe中存在多行數(shù)據(jù)).
除上列 a, b 二語法外, AWK程序中它處若出現(xiàn)像 "date", "cls", "ls"...
等字符串, AWK只當(dāng)成一般字符串處理之.
建立如下檔案并取名為 reformat2.awk
# 程序 reformat2.awk
# 這程序用以練習(xí)AWK中的pipe
BEGIN {
"date" | getline # Shell 執(zhí)行 ``date''. getline 取得結(jié)果
并以$0記錄
print " Today is " , $2, $3 >"today_rpt2"
print "=========================" > "today_rpt2"
print `` ID Number Arrival Time'' >``today_rpt2''
close( "today_rpt2" )
}
{printf( "%s \%s\n", $1 ,$2 )"sort +2n >>today_rpt2"}
執(zhí)行如下命令:
awk -f reformat2.awk arr.dat
執(zhí)行后, 系統(tǒng)會自動將 sort 后的數(shù)據(jù)加( Append; 因為使用 `` >>'')
到檔案 today_rpt2末端. today_rpt2 內(nèi)容如下 :
Today is Sep 17
=========================
ID Number Arrival Time
1005 8:12
1006 7:45
1008 8:01
1012 7:46
1025 7:27
1028 7:49
1029 7:57
1034 7:26
1042 7:59
1051 7:51
1052 8:05
1101 7:32
說 明 :
AWK程序由三個主要部分構(gòu)成 :
[ i.] Pattern { Action} 指令
[ ii.] 函數(shù)主體. 例如 : function double( x ){ return 2*x }
(參考第11節(jié) Recursive Program )
[ iii.] Comment ( 以 # 開頭識別之 )
AWK 的輸入指令 getline, 每次讀取一列數(shù)據(jù). 若getline之后
未接任何變量, 則所讀入之資料將以$0 紀(jì)錄, 否則以所指定的變量
儲存之.
[ 以本例而言] :
執(zhí)行 "date" | getline 后,
$0 之值為 "Wed Aug 17 11:04:44 EAT 1994"
當(dāng) $0 之值被更新時, AWK將自動更新相關(guān)的內(nèi)建變量, 如 :
$1,$2,..,NF.故 $2 之值將為"Aug", $3之值將為"17".
(有少數(shù)舊版之AWK不允許即使用者自行更新(update)$0之值,或者
update$0時,它不會自動更新 $1,$2,..NF. 這情況下, 可改用gawk,
或nawk. 否則使用者也可自行以AWK字符串函數(shù)split()來分隔$0上
的資料)
本程序中 printf() 指令會被執(zhí)行12次( 因為有arr.dat中有12筆
數(shù)據(jù)), 但讀者不用 擔(dān)心數(shù)據(jù)被重復(fù)sort了12次. 當(dāng)AWK結(jié)束該程序
時才會 close 這個 pipe , 此時才將這12筆數(shù)據(jù)一次送往系統(tǒng),
并呼叫 "sort +2n >> today_rpt2" 處理之.
AWK提供另一個叫用Shell命令的方法, 即使用AWK函數(shù)
system("shell命令"
例如 :
awk '
BEGIN{
system("date > date.dat"
getline <date.dat
print "Today is ", $2, $3
}
'
但使用 system( "shell 命令" ) 時, AWK無法直接將執(zhí)行中的
部分?jǐn)?shù)據(jù)輸出給Shell 命令. 且 Shell 命令執(zhí)行的結(jié)果也無法直接
輸入到AWK中.
執(zhí)行 AWK 程序的幾種方式
本小節(jié)中描述如何將AWK程序直接寫在 shell script 之中. 此后
使用者執(zhí)行 AWK 程序時, 就不需要每次都鍵入
`` awk -f program datafile''
script 中還可包含其它 Shell 命令, 如此更可增加執(zhí)行過程的自動化.
建立一個簡單的AWK程序 mydump.awk, 如下 :
{print}
這個程序執(zhí)行時會把數(shù)據(jù)文件的內(nèi)容 print 到屏幕上( 與cat功用類似 ).
print 之后未接任何參數(shù)時, 表示 ``print $0''.
若欲執(zhí)行該AWK程序, 來印出檔案 today_rpt1 及 today_rpt2 的內(nèi)容時,
必須于 UNIX 的命令列上執(zhí)行下列命令 :
方式一 awk -f mydump.awk today_rpt1 today_rpt2
方式二 awk ' print ' today_rpt1 today_rpt2第二種方式系將AWK
程序直接寫在 Shell 的命令列上, 這種方式僅適合較短的AWK程序.
方式三 建立如下之 shell script, 并取名為 mydisplay,
awk ' # 注意 , awk 與 ' 之間須有空白隔開
{print}
' $* # 注意 , ' 與 $* 之間須有空白隔開
執(zhí)行 mydisplay 之前, 須先將它改成可執(zhí)行的檔案(此步驟
往后不再贅述). 請執(zhí)行如下命令:
$ chmod +x mydisplay
往后使用者就可直接把 mydisplay 當(dāng)成指令, 來display任何檔案.
例如 :
$ mydisplay today_rpt1 today_rpt2
說 明 :
在script檔案 mydisplay 中, 指令``awk''與第一個 '
之間須有空格(Shell中并無`` awk' ''指令).
第一個 ' 用以通知 Shell 其后為AWK程序.
第二個 ' 則表示 AWK 程序結(jié)束.
故AWK程序中一律以"括住字符串或字符, 而不使用 ' ,
以免Shell混淆.
$* 為 shell script中之用法, 它可用以代表命令列上 ``mydisplay
之后的所有參數(shù)''.
例如執(zhí)行 :
$ mydisplay today_rpt1 today_rpt2
事實上 Shell 已先把該指令轉(zhuǎn)換成 :
awk '
{ print}
' today_rpt1 today_rpt2
本例中, $* 用以代表 ``today_rpt1 today_rpt2''. 在Shell的語法中,
可用 $1 代表第一個參數(shù), $2 代表第二個參數(shù). 當(dāng)不確定命令列上的
參數(shù)個數(shù)時, 可使用 $* 表之.
AWK命令列上可同時指定多個數(shù)據(jù)文件.
以awk -f dump.awk today_rpt1 today_rpt2hf 為例
AWK會先處理today_rpt1, 再處理 today_rpt2. 此時若檔案
無法開啟, 將造成錯誤.
例如: 未存在檔案"file_no_exist", 則執(zhí)行 :
awk -f dump.awk file_no_exit
將產(chǎn)生Runtime Error(無法開啟檔案).
但某些AWK程序 ``僅'' 包含以 BEGIN 為Pattern的指令. 執(zhí)行這種
AWK程序時, AWK并不須開啟任何數(shù)據(jù)文件.此時命令列上若指定
一個不存在的數(shù)據(jù)文件,并不會產(chǎn)生 ``無法開啟檔案''的錯誤.(事實上
AWK并未開啟該檔案)
例如執(zhí)行:
awk 'BEGIN {print "Hello,World!!"} ' file_no_exist
該程序中僅包含以 BEGIN 為 Pattern 之 Pattern {actions}, AWK
執(zhí)行時并不會開啟任何數(shù)據(jù)文件; 故不會因不存在檔案file_no_exit而
產(chǎn)生 `` 無法開啟檔案''的錯誤.
AWK會將 Shell 命令列上AWK程序(或 -f 程序文件名)之后的所有
字符串, 視為將輸入AWK進(jìn)行處理的數(shù)據(jù)文件文件名.
若執(zhí)行AWK的命令列上 ``未指定任何數(shù)據(jù)文件文件名'', 則將stdin視為
輸入之?dāng)?shù)據(jù)來源, 直到輸入end of file( Ctrl-D )為止.
讀者可以下列程序自行測試, 執(zhí)行如下命令 :
$awk -f dump.awk (未接任何資料文件文件名)
或
$ mydisplay (未接任何資料文件文件名)
將會發(fā)現(xiàn) : 此后鍵入的任何數(shù)據(jù)將逐行復(fù)印一份于屏幕上. 這情況
不是機(jī)器當(dāng)機(jī) ! 是因為AWK程序正處于執(zhí)行中. 它正按程序指示,
將讀取數(shù)據(jù)并重新dump一次; 只因執(zhí)行時未指定數(shù)據(jù)文件文件名, 故AWK
便以stdin(鍵盤上的輸入)為數(shù)據(jù)來源.
讀者可利用這個特點(diǎn), 設(shè)計可與AWK程序interactive talk的程序.
改變 AWK 切割字段的方式 & 使用者定義函數(shù)
AWK不僅能自動分割字段, 也允許使用者改變其字段切割方式以
適應(yīng)各種格式之需要. 使用者也可自定函數(shù), 若有需要可將該函數(shù)
單獨(dú)寫成一個檔案,以供其它AWK程序叫用.
范例 : 承接 6.2 的例子, 若八點(diǎn)為上班時間, 請加注 ``*''于遲到記錄
之前, 并計算平均上班時間.
分 析:
因八點(diǎn)整到達(dá)者,不為遲到, 故僅以到達(dá)的小時數(shù)做判斷是不夠的;
仍應(yīng)參考到達(dá)時的分鐘數(shù). 若 ``將到達(dá)時間轉(zhuǎn)換成以分鐘為單位'',
不僅易于判斷是否遲到, 同時也易于計算到達(dá)平均時間.
到達(dá)時間($2)的格式為 dd:dd 或 d:dd; 數(shù)字當(dāng)中含有一個 ":".
但文數(shù)字交雜的數(shù)據(jù)AWK無法直接做數(shù)學(xué)運(yùn)算. (注: AWK中字符串
"26"與數(shù)字26, 并無差異, 可直接做字符串或數(shù)學(xué)運(yùn)算, 這是AWK重要
特色之一. 但AWK對文數(shù)字交雜的字符串無法正確進(jìn)行數(shù)學(xué)運(yùn)算).
解決之方法 :
方法一.
對到達(dá)時間($2) d:dd 或 dd:dd 進(jìn)行字符串運(yùn)算,分別取出到達(dá)的小時數(shù)
及分鐘數(shù).
首先判斷到達(dá)小時數(shù)為一位或兩位字符,再呼叫函數(shù)分別截取分鐘數(shù)
及小時數(shù).
此解法需使用下列AWK字符串函數(shù):
length( 字符串 ) : 傳回該字符串之長度.
substr( 字符串,起始位置 ,長度 ) :傳回從起始位置起, 指定長度
之子字符串. 若未指定長度, 則傳回起始位置到自串末尾之子字符串.
所以:
小時數(shù) = substr( $2, 1, length($2) - 3 )
分鐘數(shù) = substr( $2, length($2) - 2 )
[方法二]
改變輸入列字段的切割方式, 使AWK切割字段后分別將
小時數(shù)及分鐘數(shù)隔開于二個不同的字段.
字段分隔字符 FS (field seperator) 是AWK的內(nèi)建變數(shù),
其默認(rèn)值是空白及tab. AWK每次切割字段時都會先參考
FS 的內(nèi)容. 若把":"也當(dāng)成分隔字符, 則AWK 便能自動把
小時數(shù)及分鐘數(shù)分隔成不同的字段.
故令
FS = "[ \t:]+" (注 : [ \t:]+ 為一Regular Expression )
Regular Expression 中使用中括號 [ ... ] 表一字符集合,
用以表示任意一個位于兩中括號間的字符.
故可用``[ \t:]''表示 一個 空白 , tab 或 ``:''
Regular Expression中使用 ``+'' 形容其前方的字符可出現(xiàn)一次
或一次以上.
故 ``[ \t:]+'' 表示由一個或多個 ``空白, tab 或 : '' 所組成的字符串.
設(shè)定 FS =''[ \t:]+'' 后, 資料列如 : ``1034 7:26'' 將被分割成3個字段
第一欄 第二欄 第三欄
$1 $2 $3
1034 7 26
getline var 見 注一 變量 var(var省略時,表示置于$0)
明顯地, AWK程序中使用方法一比方法二更簡潔方便. 本范例中采用
方法二,也藉此示范改變字段切割方式之用途.
編寫AWK程序 reformat3, 如下 :
awk '
BEGIN {
{FS= "[ \t:]+" #改變字段切割的方式
"date" | getline # Shell 執(zhí)行 ``date''. getline 取得結(jié)果以$0紀(jì)錄
print " Today is " ,$2, $3 > "today_rpt3"
print "=========================">"today_rpt3"
print `` ID Number Arrival Time'' > ``today_rpt3''
close( "today_rpt3" )
}
{
#已更改字段切割方式, $2表到達(dá)小時數(shù), $3表分鐘數(shù)
arrival = HM_to_M($2, $3)
printf(" %s %s:%s %s\n", $1,$2, $3
, arrival > 480 ? "*": " " ) | "sort +0n>>today_rpt3"
total += arrival
END {
close("today_rpt3" #參考本節(jié)說明 5
close("sort +0n >> today_rpt3"
printf(" Average arrival time : %d:%d\n",
total/NR/60, (total/NR)%60 ) >> "today_rpt3"
}
function HM_to_M( hour, min ){
return hour*60 + min
}
' $*
并執(zhí)行如下指令 :
$ reformat3 arr.doc
執(zhí)行后,檔案 today_rpt3 的內(nèi)容如下:
Today is Sep 21
=========================
ID Number Arrival Time
1005 8:12 *
1006 7:45
1008 8:01 *
1012 7:46
1025 7:27
1028 7:49
1029 7:57
1034 7:26
1042 7:59
1051 7:51
1052 8:05 *
1101 7:32
Average arrival time : 7:49
{verbatim}
說 明 :
AWK 中亦允許使用者自定函數(shù). 函數(shù)定義方式請參考本程序,
function 為 AWK 的保留字.
HM_to_M( ) 這函數(shù)負(fù)責(zé)將所傳入之小時及分鐘數(shù)轉(zhuǎn)換成
以分鐘為單位. 使用者自定函數(shù)時, 還有許多細(xì)節(jié)須留心, 如
data scope,..
( 請參考 第十節(jié) Recursive Program)
AWK中亦提供與 C 語言中相同的 Conditional Operator. 上式
printf()中使用arrival >480 ? "*" : " "}即為一例
若 arrival 大于 480 則return "*" , 否則return " ".
% 為AWK之運(yùn)算子(operator), 其作用與 C 語言中之 % 相同
(取余數(shù)).
NR(Number of Record) 為AWK的內(nèi)建變數(shù). 表AWK執(zhí)行該程序
后所讀入的紀(jì)錄筆數(shù).
AWK 中提供的 close( )指令, 語法如下(有二種) :
close( filename )
close( 置于pipe之前的command )
為何本程序使用了兩個 close( ) 指令 :
指令 close( "sort +2n >> today_rpt3" ), 其意思為 close 程序中
置于 "sort +2n >> today_rpt3 " 之前的 Pipe, 并立刻呼叫 Shell 來
執(zhí)行"sort +2n >> today_rpt3".
(若未執(zhí)行這指令, AWK必須于結(jié)束該程序時才會進(jìn)行上述動作;
則這12筆sort后的數(shù)據(jù)將被 append 到檔案 today_rpt3 中
"Average arrival time : ..." 的后方)
因為 Shell 排序后的數(shù)據(jù)也要寫到 today_rpt3, 所以AWK必須
先關(guān)閉使用中的today_rpt3 以利 Shell 正確將排序后的數(shù)據(jù)
append 到today_rpt3否則2個不同的 process 同時開啟一
檔案進(jìn)行輸出將會產(chǎn)生不可預(yù)期的結(jié)果.
讀者應(yīng)留心上述兩點(diǎn),才可正確控制數(shù)據(jù)輸出到檔案中的順序.
指令 close("sort +0n >> today_rpt3" 中字符串 "sort +0n >> today_rpt3"
須與 pipe | 后方的 Shell Command 名稱一字不差, 否則AWK將視為
二個不同的 pipe.
讀者可于BEGIN{}中先令變數(shù) Sys_call = "sort +0n >> today_rpt3",
程序中再一律以 Sys_call 代替該字符串.
使用 getline 來讀取數(shù)據(jù)
范 例 : 承上題,從檔案中讀取當(dāng)月遲到次數(shù), 并根據(jù)當(dāng)日出勤狀況
更新遲到累計數(shù).(按不同的月份累計于不同的檔案)
分 析:
程序中自動抓取系統(tǒng)日期的月份名稱, 連接上``late.dat'',
形成累計遲到次數(shù)的文件名稱(如 : Jullate.dat,...), 并以變數(shù)
late_file紀(jì)錄該文件名.
累計遲到次數(shù)的檔案中的數(shù)據(jù)格式為 :
員工代號(ID) 遲到次數(shù)
例如, 執(zhí)行本程序前檔案 Auglate.dat 的內(nèi)容為 :
1012 0
1006 1
1052 2
1034 0
1005 0
1029 2
1042 0
1051 0
1008 0
1101 0
1025 1
1028 0
編寫程序 reformat4.awk 如下:
awk '
BEGIN {
Sys_Sort = "sort +0n >> today_rpt4"
Result = "today_rpt4"
# 改變字段切割的方式
# 令 Shell執(zhí)行``date''; getline 讀取結(jié)果,并以$0紀(jì)錄
FS = "[\t:]+"
"date" | getline
print " Today is " , $2, $3 >Result
print "=========================" > Result
print `` ID Number Arrival Time'' > Result
close( Result )
# 從文件按中讀取遲到數(shù)據(jù), 并用數(shù)組cnt[ ]記錄. 數(shù)組cnt[ ]中以
員工代號為# 注標(biāo), 所對應(yīng)的值為該員工之遲到次數(shù).
late_file = $2 "late.dat"
while( getline < late_file >0 ) cnt[$1] = $2
close( late_file )
}
{
# 已更改字段切割方式, $2表小時數(shù),$3表分鐘數(shù)
arrival = HM_to_M($2, $3)
if( arrival > 480 ){
mark = "*" # 若當(dāng)天遲到,應(yīng)再增加其遲到次數(shù), 且令
mark 為''*''.cnt[$1]++ }
else mark = " "
# message 用以顯示該員工的遲到累計數(shù), 若未曾遲到
message 為空字符串
message = cnt[$1] ? cnt[$1] " times" : ""
printf("%s%2d:%2d %5s %s\n", $1, $2, $3, mark,
message ) | Sys_Sort
total += arrival
}
END {
close( Result )
close( Sys_Sort )
printf(" Average arrival time : %d:%d\n", total/NR/60,
(total/NR)%60 ) >> Result
#將數(shù)組cnt[ ]中新的遲到數(shù)據(jù)寫回檔案中
for( any in cnt )
print any, cnt[any] > late_file
}
function HM_to_M( hour, min ){
return hour*60 + min
}
' $*
執(zhí)行后, today_rpt4 之內(nèi)容如下 :
Today is Aug 17
================================
ID Number Arrival Time
1005 8:12 * 1 times
1006 7:45 1 times
1008 8: 1 * 1 times
1012 7:46
1025 7:27 1 times
1028 7:49
1029 7:57 2 times
1034 7:26
1042 7:59
1051 7:51
1052 8: 5 * 3 times
1101 7:32
Average arrival time : 7:49
*
說 明 :
latefile 是一變量, 用以記錄遲到次數(shù)的檔案之檔名.
latefile之值由兩部分構(gòu)成, 前半部是當(dāng)月月份名稱(由呼叫
"date"取得)后半部固定為"late.dat" 如 : Junlate.dat.
指令 getline <latefile 表由latefile所代表的檔案中
讀取一筆紀(jì)錄, 并存放于$0.
若使用者可自行把數(shù)據(jù)放入$0, AWK會自動對這新置入 $0 的數(shù)據(jù)
進(jìn)行字段分割. 之后程序中可用$1, $2,..來表示該筆資料的第一欄,
第二欄,..,
(注: 有少數(shù)AWK版本不容許使用者自行將數(shù)據(jù)置于 $0, 遇此情況可改
用gawk或nawk)
執(zhí)行g(shù)etline指令時, 若成功讀取紀(jì)錄,它會傳回1. 若遇到檔案結(jié)束,
它傳回0; 無法開啟檔案則傳回-1.
利用 while( getline < filename >0 ) {....}可讀入檔案中
的每一筆數(shù)據(jù)并予處理. 這是AWK中user自行讀取檔案數(shù)據(jù)的
一個重要模式.
數(shù)組 late_cnt[ ] 以員工ID. 當(dāng)注標(biāo)(index), 其對應(yīng)值表其遲到的
次數(shù).
執(zhí)行結(jié)束后, 利用 for(Variable in array ){..}之語法
for( any in late_cnt ) print any, late_cnt[any]> latefile
將更新過的遲到數(shù)據(jù)重新寫回記錄遲到次數(shù)之檔案. 該語法于第5節(jié)
中曾有說明.
處理 Multi-line 記錄
AWK每次從數(shù)據(jù)文件中只讀取一筆Record, 進(jìn)行處理. AWK系依照其內(nèi)建變量 RS(Record Separator) 的定義將檔案中的數(shù)據(jù)分隔成一筆一筆的Record. RS 的默認(rèn)值是 "\n"(跳行符號), 故平常AWK中一行數(shù)據(jù)就是一筆 Record. 但有些檔案中一筆Record涵蓋了數(shù)行數(shù)據(jù), 這種情況下不能再以 "\n" 來分隔Records. 最常使用的方法是相鄰的Records之間改以 一個空白行 來隔開. 在AWK程序中, 令 RS = ""(空字符串)后, AWK把會空白行當(dāng)成來檔案中Record的分隔符. 顯然AWK對 RS = "" 另有解釋方式,簡略描述如下, 當(dāng) RS = "" 時 :
數(shù)個并鄰的空白行, AWK僅視成一個單一的Record Saparator. (AWK不會于兩個緊并的空白行之間讀取一筆空的Record)
AWK會略過(skip)檔首或檔末的空白行. 故不會因為檔首或檔末的空白行,造成AWK多讀入了二筆空的數(shù)據(jù).
請觀察下例,首先建立一個數(shù)據(jù)文件 week.rpt如下: 張長弓 GNUPLOT 入門 吳國強(qiáng) Latex 簡介 VAST-2 使用手冊 mathematica 入門 李小華 AWK Tutorial Guide Regular Expression 該檔案檔首有數(shù)列空白行, 各筆Record之間使用一個或數(shù)個空白行隔開. 讀者請細(xì)心觀察, 當(dāng) RS = "" 時, AWK讀取該數(shù)據(jù)文件之方式. 編輯一個AWK程序檔案 make_report如下: awk ' BEGIN { FS = "\n" RS = "" split( "一. 二. 三. 四. 五. 六. 七. 八. 九.", C\_Number, " " ) } { printf("\n%s 報告人 : %s \n",C_Number[NR],$1) for( i=2; i { >}= NF; i++) printf(" %d. %s\n", i-1, $i) } ' $ 執(zhí)行 $ make_report week.rpt 屏幕產(chǎn)生結(jié)果如下: 一. 報告人 : 張長弓 1. GNUPLOT 入門 二. 報告人 : 吳國強(qiáng) 1. Latex 簡介 2. VAST-2 使用手冊 3. mathematica 入門 三. 報告人 : 李小華 1. AWK Tutorial Guide 2. Regular Expression 說明:
本程序同時也改變字段分隔字符( FS= "\n" , 如此一筆數(shù)據(jù)中的每一行都是一個field. 例如 : AWK讀入的第一筆 Record 為張長弓 GNUPLOT 入門 其中 $1 指的是"張長弓", $2 指的是"GNUPLOT 入門"
上式中的C\_Number[ ]是一個數(shù)組(array), 用以記錄中文數(shù)字. 例如 : C\_Number[1] = "一", C\_Number[2] = "二" 這過程使用AWK字符串函數(shù) split( ) 來把中文數(shù)字放進(jìn)數(shù)組 Number[ ]中. 函數(shù) split( )用法如下 : split( 原字符串, 數(shù)組名, 分隔字符(field separator) ) : AWK將依所指定的分隔字符(field separator)分隔原字符串成一個個的字段(field), 并以指定的 數(shù)組 記錄各個被分隔的字段
如何讀取命令列上的參數(shù)
大部分的應(yīng)用程序都容許使用者于命令之后增加一些選擇性的參數(shù).
執(zhí)行AWK時這些參數(shù)大部分用于指定數(shù)據(jù)文件文件名, 有時希望在程序
中能從命令列上得到一些其它用途的數(shù)據(jù). 本小節(jié)中將敘述如何在
AWK程序中取用這些參數(shù).
建立檔案如下, 命名為 see_arg :
{
awk '
BEGIN {
for( i=0; i<ARGC ; i++)
print ARGV # 依次印出AWK所紀(jì)錄的參數(shù)
}
'$*
執(zhí)行如下命令 :
$ see_arg first-arg second-arg
結(jié)果屏幕出現(xiàn) :
awk
first-arg
second-arg
說明 :
ARGC, ARGV[ ] 為AWK所提供的內(nèi)建變量.
ARGC : 為一整數(shù). 代表命令列上, 除了選項-v, -f 及其對應(yīng)
的參數(shù)之外所有參數(shù)的數(shù)目.
ARGV[ ] : 為一字符串?dāng)?shù)組. ARGV[0],ARGV[1],...ARGV[ARGC-1].
分別代表命令列上相對應(yīng)的參數(shù).
例如, 當(dāng)命令列為 :
$awk -vx=36 -f program1 data1 data2
或
awk '{ print $1 ,$2 }' data1 data2
其 ARGC 之值為 3
ARGV[0] 之值為 "awk"
ARGV[1] 之值為 "data1"
ARGV[2] 之值為 "data2"
命令列上的 "-f program1", " -vx=36", 或程序部分 '{ print $1, $2}'
都不會列入 ARGC 及 ARGV[ ] 中.
AWK 利用 ARGC 來判斷應(yīng)開啟的數(shù)據(jù)文件個數(shù).
但使用者可強(qiáng)行改變 ARGC; 當(dāng) ARGC 之值被使用者設(shè)為 1 時;
AWK將被蒙騙,誤以為命令列上并無數(shù)據(jù)文件文件名, 故不會以 ARGV[1],
ARGV[2],..為文件名來開文件讀取數(shù)據(jù); 但于程序中仍可藉由 ARGV[1],
ARGV[2],..來取得命令列上的資料.
某一程序 test1.awk 如下 :
BEGIN{
number = ARGC #先用number 記住實際的參數(shù)個數(shù).
ARGC = 2 # 自行更改 ARGC=2, AWK將以為只有一個
資料文件
# 仍可藉由ARGV[ ]取得命令列上的資料.
for( i=2; i< number; i++) data = ARGV
}
........
于命令列上鍵入
$awk -f test1.awk data_file apple orange
執(zhí)行時 AWK 會開啟數(shù)據(jù)文件 data_file 以進(jìn)行處理. 不會開啟以
apple,orange 為檔名的檔案(因為 ARGC 被改成2). 但仍可藉由
ARGV[2], ARGV[3]取得命令列上的參數(shù) apple, orange
可以下列命令來達(dá)成上例的效果.
$awk -f test2.awk -v data[2]="apple" -v data[3]="orange" data_file
撰寫可與使用者相互交談的 AWK 程序
執(zhí)行AWK程序時, AWK會自動由檔案中讀取數(shù)據(jù)來進(jìn)行
處理, 直到檔案結(jié)束.只要將AWK讀取數(shù)據(jù)的來源改成鍵盤輸入,
便可設(shè)計與AWK interactive talk 的程序.
本節(jié)將提供一個該類程序的范例.
[范例 :] 本節(jié)將撰寫一個英語生字測驗的程序, 它將印出中文字意,
再由使用者回答其英語生字.
首先編輯一個數(shù)據(jù)擋 test.dat (內(nèi)容不拘,格式如下)
apple 蘋果
orange 柳橙
banana 香蕉
pear 梨子
starfruit 楊桃
bellfruit 蓮霧
kiwi 奇異果
pineapple 菠蘿
watermelon 西瓜
編輯AWK程序"c2e"如下:
awk '
BEGIN {
while( getline < ARGV[1] ){ #由指定的檔案中讀取測驗數(shù)據(jù)
English[++n] = $1 # 最后, n 將表示題目之題數(shù)
Chinese[n] = $2
}
ARGV[1] = "-" # "-"表示由stdin(鍵盤輸入)
srand() # 以系統(tǒng)時間為隨機(jī)數(shù)啟始的種子
question( ) #產(chǎn)生考題
}
{# AWK自動讀入由鍵盤上輸入的數(shù)據(jù)(使用者回答的答案)
if($1 != English[ind] )
print "Try again!"
else{
print "\nYou are right !! Press Enter to Continue --- "
getline
question( )#產(chǎn)生考題
}
}
function question(){
ind = int(rand( )* n) + 1 #以隨機(jī)數(shù)選取考題
system("clear"
print " Press\"ctrl-d\" to exit"
printf("\n%s ", Chinese[ind] " 的英文生字是: "
}
'$*
執(zhí)行時鍵入如下指令 :
$c2e test.dat
屏幕將產(chǎn)生如下的畫面:
Press "ctrl-d " to exit
蓮霧 的英文生字是:
若輸入 bellfruit
程序?qū)a(chǎn)生
You are right !! Press Enter to Continue ---
}
說 明 :
參數(shù) test.dat (ARGV[1]) 表示儲存考題的數(shù)據(jù)文件文件名.
AWK 由該檔案上取得考題資料后, 將 ARGV[1] 改成 "-".
"-" 表示由 stdin(鍵盤輸入) 數(shù)據(jù). 鍵盤輸入數(shù)據(jù)的結(jié)束符號 (End of file)
是 Ctrl-d. 當(dāng) AWK 讀到 Ctrl-d 時就停止由 stdin 讀取數(shù)據(jù).
AWK的數(shù)學(xué)函數(shù)中提供兩個與隨機(jī)數(shù)有關(guān)的函數(shù).
rand( ) : 傳回介于 0與1之間的(近似)隨機(jī)數(shù)值. 0
使用 AWK 撰寫 Recusive Program
AWK 中除了函數(shù)的參數(shù)列(Argument List)上的參數(shù)(Arguments)外,
所有變量不管于何處出現(xiàn)全被視為 Global variable. 其生命持續(xù)
至程序結(jié)束 --- 該變量不論在function外或 function內(nèi)皆可使用,
只要變量名稱相同所使用的就是同一個變量,直到程序結(jié)束.
因 Recusive 函數(shù)內(nèi)部的變量, 會因它呼叫子函數(shù)(本身)而重復(fù)使用,
故撰寫該類函數(shù)時, 應(yīng)特別留心.
例如 : 執(zhí)行
awk '
BEGIN
{
x = 35
y = 45
test_variable( x )
printf("Return to main : arg1= %d, x= %d, y= %d, z= %d\n",
arg1, x, y, z)
}
function test_variable( arg1 )
{
arg1++ # arg1 為參數(shù)列上的參數(shù), 是local variable. 離開此函數(shù)
后將消失.
y ++ # 會改變主式中的變量 y
z = 55 # z 為該函數(shù)中新使用的變量, 主程序中變量 z 仍可被使用.
printf("Inside the function: arg1=%d,x=%d, y=%d, z=%d\n",
arg1, x, y, z)
} '
結(jié)果屏幕印出
Inside the function: arg1= 36,x= 35, y= 46, z= 55
Return to main : arg1= 0, x= 35, y= 46, z= 55
由上可知 :
函數(shù)內(nèi)可任意使用主程序中的任何變量.
函數(shù)內(nèi)所啟用的任何變量(除參數(shù)外), 于該函數(shù)之外依然可以使用.
此特性優(yōu)劣參半, 最大的壞處是式中的變量不易被保護(hù), 特別是
recursive呼叫本身, 執(zhí)行子函數(shù)時會破壞父函數(shù)內(nèi)的變量.
權(quán)變的方法是 : 在函數(shù)的 Argument list 上虛列一些 Arguments.
函數(shù)執(zhí)行中使用這些虛列的 Arguments 來記錄不想被破壞的數(shù)據(jù),
如此執(zhí)行子函數(shù)時就不會破壞到這些數(shù)據(jù). 此外AWK 并不會檢查,
呼叫函數(shù)時所傳遞的參數(shù)個數(shù)是否一致.
例如 : 定義 recursive function 如下 :
function demo( arg1 )# 最常見的錯誤例子
........
for(i=1; i< 20 ; i++){
demo(x)
# 又呼叫本身. 因為 i 是 global variable, 故執(zhí)行完該子函數(shù)后
# 原函數(shù)中的 i 已經(jīng)被壞, 故本函數(shù)無法正確執(zhí)行.
.......
}
..........
}
可將上列函數(shù)中的 i 虛列在該函數(shù)的參數(shù)列上, 如此 i 便是一個
local variable, 不會因執(zhí)行子函數(shù)而被破壞.
將上列函數(shù)修改如下:
function demo( arg1, i )
{
......
for(i=1; i< 20; i++)
{
demo(x)#AWK不會檢查呼叫函數(shù)時, 所傳遞的參數(shù)個數(shù)是否一致
.....
}
}
$0, $1,.., NF, NR,..也都是 global variable, 讀者于 recusive function
中若有使用這些內(nèi)建變量, 也應(yīng)另外設(shè)立一些 local variable 來保存,
以免被破壞.
范例 :以下是一個常見的 Recursive 范例. 它要求使用者輸入一串元素
(各元素間用空白隔開) 然后印出這些元素所有可能的排列.
編輯如下的AWK式, 取名為 permu
awk '
BEGIN
{
print "請輸入排列的元素,各元素間請用空白隔開"
getline
permutation($0, ""
printf("\n共 %d 種排列方式\n", counter)
}
function permutation( main_lst, buffer, new_main_lst, nf, i, j )
{
$0 = main_lst # 把main_lst指定給$0之后AWK將自動進(jìn)行
字段分割.
nf = NF # 故可用 NF 表示 main_lst 上存在的元素個數(shù).
# BASE CASE : 當(dāng)main_lst只有一個元素時.
if( nf == 1)
{
print buffer main_lst # buffer的內(nèi)容連接(concate)上 main_lst 就
counter++ # 是完成一次排列的結(jié)果
return
}
# General Case : 每次從 main\_lst 中取出一個元素放到buffer中
# 再用 main_lst 中剩下的元素 (new_main_lst) 往下進(jìn)行排列
else for( i=1; i< =nf ;i++)
{
$0 = main_lst # $0($1,$2,..$j,,)為Global variable已被壞, 故重新
# 把 main\_lst 指定給\$0, 令A(yù)WK再做一次字段分割
new_main_lst = ""
for(j=1; j< =nf; j++) # concate new_main_lst
if( j != i ) new_main_lst = new_main_lst " " $j
permutation( new_main_lst, buffer " " $i )
}
}
'$*
執(zhí)行 $ permu
屏幕上出現(xiàn)
請輸入排列的元素,各元素間請用空白隔開
若輸入 1 2 3 結(jié)果印出
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1
共 6 種排列方式
說 明 :
有些較舊版的AWK,并不容許使用者指定$0之值. 此時可改用
gawk, 或 nawk.
否則也可自行使用 split() 函數(shù)來分割 main_lst.
為避免執(zhí)行子函數(shù)時破壞 new_main_lst, nf, i, j 故把這些變數(shù)
也列于參數(shù)列上. 如此, new_main_lst, nf, i, j 將被當(dāng)成 local variable,
而不會受到子函數(shù)中同名的變量影響. 讀者宣告函數(shù)時,參數(shù)列上
不妨將這些 ``虛列的參數(shù)'' 與真正用于傳遞信息的參數(shù)間以較長
的空白隔開, 以便于區(qū)別.
AWK 中欲將字符串concatenation(連接)時, 直接將兩字符串并置
即可(Implicit Operator).
例如 :
awk '
BEGIN{
A = "This "
B = "is a "
C = A B "key." # 變量A與B之間應(yīng)留空白,
否則''AB''將代表另一新變量.
print C
}
}
結(jié)果將印出
This is a key.
AWK使用者所撰寫的函數(shù)可再reuse, 并不需要每個AWK式中
都重新撰寫.
將函數(shù)部分單讀編寫于一檔案中, 當(dāng)需要用到該函數(shù)時再以下列方式
include進(jìn)來.
$ awk -f 函數(shù)襠名 -f AWK主程序文件名 數(shù)據(jù)文件文件名
Appendix A Patterns
AWK 藉由判斷 Pattern 之值來決定是否執(zhí)行其后所對應(yīng)的
Actions.這里列出幾種常見的 Pattern : |
|