awk教程入门与实例练习(二)
多行記錄
awk 是一種用于讀取和處理結(jié)構(gòu)化數(shù)據(jù)(如系統(tǒng)的 /etc/passwd 文件)的極佳工具。/etc/passwd 是 UNIX 用戶數(shù)據(jù)庫,并且是用冒號定界的文本文件,它包含許多重要信息,包括所有現(xiàn)有用戶帳戶和用戶標(biāo)識,以及其它信息。在我的前一篇文章中,我演示了 awk 如何輕松地分析這個文件。我們只須將 FS(字段分隔符)變量設(shè)置成 “:”。
正確設(shè)置了 FS 變量之后,就可以將 awk 配置成分析幾乎任何類型的結(jié)構(gòu)化數(shù)據(jù),只要這些數(shù)據(jù)是每行一個記錄。然而,如果要分析占據(jù)多行的記錄,僅僅依靠設(shè)置 FS 是不夠的。在這些情況下,我們還需要修改 RS 記錄分隔符變量。RS 變量告訴 awk 當(dāng)前記錄什么時候結(jié)束,新記錄什么時候開始。
譬如,讓我們討論一下如何完成處理“聯(lián)邦證人保護(hù)計劃”所涉及人員的地址列表的任務(wù):
Jimmy the Weasel
100 Pleasant Drive
San Francisco, CA 12345
Big Tony
200 Incognito Ave.
Suburbia, WA 67890
理論上,我們希望 awk 將每 3 行看作是一個獨立的記錄,而不是三個獨立的記錄。如果 awk 將地址的第一行看作是第一個字段 ($1),街道地址看作是第二個字段 ($2),城市、州和郵政編碼看作是第三個字段 $3,那么這個代碼就會變得很簡單。以下就是我們想要得到的代碼:
BEGIN {
FS=”/n”
RS=”"
}
在上面這段代碼中,將 FS 設(shè)置成 “/n” 告訴 awk 每個字段都占據(jù)一行。通過將 RS 設(shè)置成 “”,還會告訴 awk 每個地址記錄都由空白行分隔。一旦 awk 知道是如何格式化輸入的,它就可以為我們執(zhí)行所有分析工作,腳本的其余部分很簡單。讓我們研究一個完整的腳本,它將分析這個地址列表,并將每個記錄打印在一行上,用逗號分隔每個字段。
address.awk
BEGIN {
FS=”/n”
RS=”"
}
{
print $1 “, ” $2 “, ” $3
}
如果這個腳本保存為 address.awk,地址數(shù)據(jù)存儲在文件 address.txt 中,可以通過輸入 “awk -f address.awk address.txt” 來執(zhí)行這個腳本。此代碼將產(chǎn)生以下輸出:
Jimmy the Weasel, 100 Pleasant Drive, San Francisco, CA 12345
Big Tony, 200 Incognito Ave., Suburbia, WA 67890
OFS 和 ORS
在 address.awk 的 print 語句中,可以看到 awk 會連接(合并)一行中彼此相鄰的字符串。我們使用此功能在同一行上的三個字段之間插入一個逗號和空格 (”, “)。這個方法雖然有用,但比較難看。與其在字段間插入 “, ” 字符串,倒不如讓通過設(shè)置一個特殊 awk 變量 OFS,讓 awk 完成這件事。請參考下面這個代碼片斷。
print “Hello”, “there”, “Jim!”
這行代碼中的逗號并不是實際文字字符串的一部分。事實上,它們告訴 awk “Hello”、”there” 和 “Jim!” 是單獨的字段,并且應(yīng)該在每個字符串之間打印 OFS 變量。缺省情況下,awk 產(chǎn)生以下輸出:
Hello there Jim!
這是缺省情況下的輸出結(jié)果,OFS 被設(shè)置成 ” “,單個空格。不過,我們可以方便地重新定義 OFS,這樣 awk 將插入我們中意的字段分隔符。以下是原始 address.awk 程序的修訂版,它使用 OFS 來輸出那些中間的 “, ” 字符串:
address.awk 的修訂版
BEGIN {
FS=”/n”
RS=”"
OFS=”, ”
}
{
print $1, $2, $3
}
awk 還有一個特殊變量 ORS,全稱是“輸出記錄分隔符”。通過設(shè)置缺省為換行 (”/n”) 的 OFS,我們可以控制在 print 語句結(jié)尾自動打印的字符。缺省 ORS 值會使 awk 在新行中輸出每個新的 print 語句。如果想使輸出的間隔翻倍,可以將 ORS 設(shè)置成 “/n/n”。或者,如果想要用單個空格分隔記錄(而不換行),將 ORS 設(shè)置成 “”。
將多行轉(zhuǎn)換成用 tab 分隔的格式
假設(shè)我們編寫了一個腳本,它將地址列表轉(zhuǎn)換成每個記錄一行,且用 tab 定界的格式,以便導(dǎo)入電子表格。使用稍加修改的 address.awk 之后,就可以清楚地看到這個程序只適合于三行的地址。如果 awk 遇到以下地址,將丟掉第四行,并且不打印該行:
Cousin Vinnie
Vinnie’s Auto Shop
300 City Alley
Sosueme, OR 76543
要處理這種情況,代碼最好考慮每個字段的記錄數(shù)量,并依次打印每個記錄。現(xiàn)在,代碼只打印地址的前三個字段。以下就是我們想要的一些代碼:
適合具有任意多字段的地址的 address.awk 版本
BEGIN {
FS=”/n”
RS=”"
ORS=”"
}
{
x=1
while ( x<NF ) {
print $x “/t”
x++
}
print $NF “/n”
}
首先,將字段分隔符 FS 設(shè)置成 “/n”,將記錄分隔符 RS 設(shè)置成 “”,這樣 awk 可以象以前一樣正確分析多行地址。然后,將輸出記錄分隔符 ORS 設(shè)置成 “”,它將使 print 語句在每個調(diào)用結(jié)尾不輸出新行。這意味著如果希望任何文本從新的一行開始,那么需要明確寫入 print “/n”。
在主代碼塊中,創(chuàng)建了一個變量 x 來存儲正在處理的當(dāng)前字段的編號。起初,它被設(shè)置成 1。然后,我們使用 while 循環(huán)(一種 awk 循環(huán)結(jié)構(gòu),等同于 C 語言中的 while 循環(huán)),對于所有記錄(最后一個記錄除外)重復(fù)打印記錄和 tab 字符。最后,打印最后一個記錄和換行;此外,由于將 ORS 設(shè)置成 “”,print 將不輸出換行。程序輸出如下,這正是我們所期望的:
我們想要的輸出。不算漂亮,但用 tab 定界,以便于導(dǎo)入電子表格
Jimmy the Weasel 100 Pleasant Drive San Francisco, CA 12345
Big Tony 200 Incognito Ave. Suburbia, WA 67890
Cousin Vinnie Vinnie’s Auto Shop 300 City Alley Sosueme, OR 76543
循環(huán)結(jié)構(gòu)
我們已經(jīng)看到了 awk 的 while 循環(huán)結(jié)構(gòu),它等同于相應(yīng)的 C 語言 while 循環(huán)。awk 還有 “do…while” 循環(huán),它在代碼塊結(jié)尾處對條件求值,而不象標(biāo)準(zhǔn) while 循環(huán)那樣在開始處求值。它類似于其它語言中的 “repeat…until” 循環(huán)。以下是一個示例:
do…while 示例
{
count=1
do {
print “I get printed at least once no matter what”
} while ( count != 1 )
}
與一般的 while 循環(huán)不同,由于在代碼塊之后對條件求值,”do…while” 循環(huán)永遠(yuǎn)都至少執(zhí)行一次。換句話說,當(dāng)?shù)谝淮斡龅狡胀?while 循環(huán)時,如果條件為假,將永遠(yuǎn)不執(zhí)行該循環(huán)。
for 循環(huán)
awk 允許創(chuàng)建 for 循環(huán),它就象 while 循環(huán),也等同于 C 語言的 for 循環(huán):
for ( initial assignment; comparison; increment ) {
code block
}
以下是一個簡短示例:
for ( x = 1; x <= 4; x++ ) {
print “iteration”,x
}
此段代碼將打印:
iteration 1
iteration 2
iteration 3
iteration 4
break 和 continue
此外,如同 C 語言一樣,awk 提供了 break 和 continue 語句。使用這些語句可以更好地控制 awk 的循環(huán)結(jié)構(gòu)。以下是迫切需要 break 語句的代碼片斷:
while 死循環(huán)
while (1) {
print “forever and ever…”
}
因為 1 永遠(yuǎn)代表是真,這個 while 循環(huán)將永遠(yuǎn)運行下去。以下是一個只執(zhí)行十次的循環(huán):
break 語句示例
x=1
while(1) {
print “iteration”,x
if ( x == 10 ) {
break
}
x++
}
這里,break 語句用于“逃出”最深層的循環(huán)。”break” 使循環(huán)立即終止,并繼續(xù)執(zhí)行循環(huán)代碼塊后面的語句。
continue 語句補(bǔ)充了 break,其作用如下:
x=1
while (1) {
if ( x == 4 ) {
x++
continue
}
print “iteration”,x
if ( x > 20 ) {
break
}
x++
}
這段代碼打印 “iteration 1″ 到 “iteration 21″,”iteration 4″ 除外。如果迭代等于 4,則增加 x 并調(diào)用 continue 語句,該語句立即使 awk 開始執(zhí)行下一個循環(huán)迭代,而不執(zhí)行代碼塊的其余部分。如同 break 一樣,continue 語句適合各種 awk 迭代循環(huán)。在 for 循環(huán)主體中使用時,continue 將使循環(huán)控制變量自動增加。以下是一個等價循環(huán):
for ( x=1; x<=21; x++ ) {
if ( x == 4 ) {
continue
}
print “iteration”,x
}
在 while 循環(huán)中時,在調(diào)用 continue 之前沒有必要增加 x,因為 for 循環(huán)會自動增加 x。
數(shù)組
如果您知道 awk 可以使用數(shù)組,您一定會感到高興。然而,在 awk 中,數(shù)組下標(biāo)通常從 1 開始,而不是 0:
myarray[1]=”jim”
myarray[2]=456
awk 遇到第一個賦值語句時,它將創(chuàng)建 myarray,并將元素 myarray[1] 設(shè)置成 “jim”。執(zhí)行了第二個賦值語句后,數(shù)組就有兩個元素了。
數(shù)組迭代
定義之后,awk 有一個便利的機(jī)制來迭代數(shù)組元素,如下所示:
for ( x in myarray ) {
print myarray[x]
}
這段代碼將打印數(shù)組 myarray 中的每一個元素。當(dāng)對于 for 使用這種特殊的 “in” 形式時,awk 將 myarray 的每個現(xiàn)有下標(biāo)依次賦值給 x(循環(huán)控制變量),每次賦值以后都循環(huán)一次循環(huán)代碼。雖然這是一個非常方便的 awk 功能,但它有一個缺點 — 當(dāng) awk 在數(shù)組下標(biāo)之間輪轉(zhuǎn)時,它不會依照任何特定的順序。那就意味著我們不能知道以上代碼的輸出是:
jim
456
還是
456
jim
套用 Forrest Gump 的話來說,迭代數(shù)組內(nèi)容就像一盒巧克力 — 您永遠(yuǎn)不知道將會得到什么。因此有必要使 awk 數(shù)組“字符串化”,我們現(xiàn)在就來研究這個問題。
數(shù)組下標(biāo)字符串化
在我的前一篇文章中,我演示了 awk 實際上以字符串格式來存儲數(shù)字值。雖然 awk 要執(zhí)行必要的轉(zhuǎn)換來完成這項工作,但它卻可以使用某些看起來很奇怪的代碼:
a=”1″
b=”2″
c=a+b+3
執(zhí)行了這段代碼后,c 等于 6。由于 awk 是“字符串化”的,添加字符串 “1″ 和 “2″ 在功能上并不比添加數(shù)字 1 和 2 難。這兩種情況下,awk 都可以成功執(zhí)行運算。awk 的“字符串化”性質(zhì)非常可愛 — 您可能想要知道如果使用數(shù)組的字符串下標(biāo)會發(fā)生什么情況。例如,使用以下代碼:
myarr[”1″]=”Mr. Whipple”
print myarr[”1″]
可以預(yù)料,這段代碼將打印 “Mr. Whipple”。但如果去掉第二個 “1″ 下標(biāo)中的引號,情況又會怎樣呢?
myarr[”1″]=”Mr. Whipple”
print myarr[1]
猜想這個代碼片斷的結(jié)果比較難。awk 將 myarr[”1″] 和 myarr[1] 看作數(shù)組的兩個獨立元素,還是它們是指同一個元素?答案是它們指的是同一個元素,awk 將打印 “Mr. Whipple”,如同第一個代碼片斷一樣。雖然看上去可能有點怪,但 awk 在幕后卻一直使用數(shù)組的字符串下標(biāo)!
了解了這個奇怪的真相之后,我們中的一些人可能想要執(zhí)行類似于以下的古怪代碼:
myarr[”name”]=”Mr. Whipple”
print myarr[”name”]
這段代碼不僅不會產(chǎn)生錯誤,而且它的功能與前面的示例完全相同,也將打印 “Mr. Whipple”!可以看到,awk 并沒有限制我們使用純整數(shù)下標(biāo);如果我們愿意,可以使用字符串下標(biāo),而且不會產(chǎn)生任何問題。只要我們使用非整數(shù)數(shù)組下標(biāo),如 myarr[”name”],那么我們就在使用關(guān)聯(lián)數(shù)組。從技術(shù)上講,如果我們使用字符串下標(biāo),awk 的后臺操作并沒有什么不同(因為即便使用“整數(shù)”下標(biāo),awk 還是會將它看作是字符串)。但是,應(yīng)該將它們稱作關(guān)聯(lián)數(shù)組 — 它聽起來很酷,而且會給您的上司留下印象。字符串化下標(biāo)是我們的小秘密。;)
數(shù)組工具
談到數(shù)組時,awk 給予我們許多靈活性。可以使用字符串下標(biāo),而且不需要連續(xù)的數(shù)字序列下標(biāo)(例如,可以定義 myarr[1] 和 myarr[1000],但不定義其它所有元素)。雖然這些都很有用,但在某些情況下,會產(chǎn)生混淆。幸好,awk 提供了一些實用功能有助于使數(shù)組變得更易于管理。
首先,可以刪除數(shù)組元素。如果想要刪除數(shù)組 fooarray 的元素 1,輸入:
delete fooarray[1]
而且,如果想要查看是否存在某個特定數(shù)組元素,可以使用特殊的 “in” 布爾運算符,如下所示:
if ( 1 in fooarray ) {
print “Ayep! It’s there.”
} else {
print “Nope! Can’t find it.”
}
下一篇
本文中,我們已經(jīng)討論了許多基礎(chǔ)知識。下一篇中,我將演示如何使用 awk 的數(shù)學(xué)運算和字符串函數(shù),以及如何創(chuàng)建您自己的函數(shù),使您完全掌握 awk 知識。我還將指導(dǎo)您創(chuàng)建支票簿結(jié)算程序。那時,我會鼓勵您編寫自己的 awk 程序。請查閱以下參考資料。
總結(jié)
以上是生活随笔為你收集整理的awk教程入门与实例练习(二)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Docker入门-简介
- 下一篇: inux中限制用户进程CPU和内存占用率