總裁兼 CEO, Gentoo Technologies, Inc.
2001 年 1 月
在這篇 awk簡介的續集中,Daniel Robbins 繼續探索awk(一種很棒但有怪異名稱的語言)。Daniel將演示如何處理多行記錄、使用循環結構,以及創建并使用 awk數組。閱讀完本文后,您將精通許多 awk的功能,而且可以編寫您自己的功能強大的 awk 腳本。
多行記錄
awk 是一種用于讀取和處理結構化數據(如系統的 /etc/passwd 文件)的極佳工具。/etc/passwd 是 UNIX 用戶數據庫,并且是用冒號定界的文本文件,它包含許多重要信息,包括所有現有用戶帳戶和用戶標識,以及其它信息。在我的 前一篇文章 中,我演示了 awk 如何輕松地分析這個文件。我們只須將 FS(字段分隔符)變量設置成 ":"。
正確設置了 FS 變量之后,就可以將 awk 配置成分析幾乎任何類型的結構化數據,只要這些數據是每行一個記錄。然而,如果要分析占據多行的記錄,僅僅依靠設置 FS 是不夠的。在這些情況下,我們還需要修改 RS 記錄分隔符變量。RS 變量告訴 awk 當前記錄什么時候結束,新記錄什么時候開始。
譬如,讓我們討論一下如何完成處理“聯邦證人保護計劃”所涉及人員的地址列表的任務:
|
理論上,我們希望 awk 將每 3 行看作是一個獨立的記錄,而不是三個獨立的記錄。如果 awk 將地址的第一行看作是第一個字段 (),街道地址看作是第二個字段 (),城市、州和郵政編碼看作是第三個字段 ,那么這個代碼就會變得很簡單。以下就是我們想要得到的代碼:
|
在上面這段代碼中,將 FS 設置成 "\n" 告訴 awk 每個字段都占據一行。通過將 RS 設置成 "",還會告訴 awk 每個地址記錄都由空白行分隔。一旦 awk 知道是如何格式化輸入的,它就可以為我們執行所有分析工作,腳本的其余部分很簡單。讓我們研究一個完整的腳本,它將分析這個地址列表,并將每個記錄打印在一行上,用逗號分隔每個字段。
address.awk
|
如果這個腳本保存為 address.awk,地址數據存儲在文件 address.txt 中,可以通過輸入 "awk -f address.awk address.txt" 來執行這個腳本。此代碼將產生以下輸出:
|
OFS 和 ORS
在 address.awk 的 print 語句中,可以看到 awk 會連接(合并)一行中彼此相鄰的字符串。我們使用此功能在同一行上的三個字段之間插入一個逗號和空格 (", ")。這個方法雖然有用,但比較難看。與其在字段間插入 ", " 字符串,倒不如讓通過設置一個特殊 awk 變量 OFS,讓 awk 完成這件事。請參考下面這個代碼片斷。
|
這行代碼中的逗號并不是實際文字字符串的一部分。事實上,它們告訴 awk "Hello"、"there" 和 "Jim!" 是單獨的字段,并且應該在每個字符串之間打印 OFS 變量。缺省情況下,awk 產生以下輸出:
|
這是缺省情況下的輸出結果,OFS 被設置成 " ",單個空格。不過,我們可以方便地重新定義 OFS,這樣 awk 將插入我們中意的字段分隔符。以下是原始 address.awk 程序的修訂版,它使用 OFS 來輸出那些中間的 ", " 字符串:
address.awk 的修訂版
|
awk 還有一個特殊變量 ORS,全稱是“輸出記錄分隔符”。通過設置缺省為換行 ("\n") 的 OFS,我們可以控制在 print 語句結尾自動打印的字符。缺省 ORS 值會使 awk 在新行中輸出每個新的 print 語句。如果想使輸出的間隔翻倍,可以將 ORS 設置成 "\n\n"?;蛘?,如果想要用單個空格分隔記錄(而不換行),將 ORS 設置成 ""。
將多行轉換成用 tab 分隔的格式
假設我們編寫了一個腳本,它將地址列表轉換成每個記錄一行,且用 tab 定界的格式,以便導入電子表格。使用稍加修改的 address.awk 之后,就可以清楚地看到這個程序只適合于三行的地址。如果 awk 遇到以下地址,將丟掉第四行,并且不打印該行:
|
要處理這種情況,代碼最好考慮每個字段的記錄數量,并依次打印每個記錄?,F在,代碼只打印地址的前三個字段。以下就是我們想要的一些代碼:
適合具有任意多字段的地址的 address.awk 版本
|
首先,將字段分隔符 FS 設置成 "\n",將記錄分隔符 RS 設置成 "",這樣 awk 可以象以前一樣正確分析多行地址。然后,將輸出記錄分隔符 ORS 設置成 "",它將使 print 語句在每個調用結尾 不 輸出新行。這意味著如果希望任何文本從新的一行開始,那么需要明確寫入 print "\n"
。
在主代碼塊中,創建了一個變量 x 來存儲正在處理的當前字段的編號。起初,它被設置成 1。然后,我們使用 while 循環(一種 awk 循環結構,等同于 C 語言中的 while 循環),對于所有記錄(最后一個記錄除外)重復打印記錄和 tab 字符。最后,打印最后一個記錄和換行;此外,由于將 ORS 設置成 "",print 將不輸出換行。程序輸出如下,這正是我們所期望的:
我們想要的輸出。不算漂亮,但用 tab 定界,以便于導入電子表格
|
循環結構
我們已經看到了 awk 的 while 循環結構,它等同于相應的 C 語言 while 循環。awk 還有 "do...while" 循環,它在代碼塊結尾處對條件求值,而不象標準 while 循環那樣在開始處求值。它類似于其它語言中的 "repeat...until" 循環。以下是一個示例:
|
與一般的 while 循環不同,由于在代碼塊之后對條件求值,"do...while" 循環永遠都至少執行一次。換句話說,當第一次遇到普通 while 循環時,如果條件為假,將永遠不執行該循環。
for 循環
awk 允許創建 for 循環,它就象 while 循環,也等同于 C 語言的 for 循環:
|
以下是一個簡短示例:
|
此段代碼將打?。?
|
break 和 continue
此外,如同 C 語言一樣,awk 提供了 break 和 continue 語句。使用這些語句可以更好地控制 awk 的循環結構。以下是迫切需要 break 語句的代碼片斷:
|
while 死循環 1
永遠代表是真,這個 while 循環將永遠運行下去。以下是一個只執行十次的循環:
|
這里,break 語句用于“逃出”最深層的循環。"break" 使循環立即終止,并繼續執行循環代碼塊后面的語句。
continue 語句補充了 break,其作用如下:
|
這段代碼打印 "iteration 1" 到 "iteration 21","iteration 4" 除外。如果迭代等于 4,則增加 x 并調用 continue 語句,該語句立即使 awk 開始執行下一個循環迭代,而不執行代碼塊的其余部分。如同 break 一樣,continue 語句適合各種 awk 迭代循環。在 for 循環主體中使用時,continue 將使循環控制變量自動增加。以下是一個等價循環:
|
在 while 循環中時,在調用 continue 之前沒有必要增加 x
,因為 for 循環會自動增加 x
。
數組
如果您知道 awk 可以使用數組,您一定會感到高興。然而,在 awk 中,數組下標通常從 1 開始,而不是 0:
|
awk 遇到第一個賦值語句時,它將創建 myarray
,并將元素 myarray[1]
設置成 "jim"。執行了第二個賦值語句后,數組就有兩個元素了。
數組迭代
定義之后,awk 有一個便利的機制來迭代數組元素,如下所示:
|
這段代碼將打印數組 myarray
中的每一個元素。當對于 for 使用這種特殊的 "in" 形式時,awk 將 myarray
的每個現有下標依次賦值給 x
(循環控制變量),每次賦值以后都循環一次循環代碼。雖然這是一個非常方便的 awk 功能,但它有一個缺點 -- 當 awk 在數組下標之間輪轉時,它不會依照任何特定的順序。那就意味著我們不能知道以上代碼的輸出是:
|
還是
|
套用 Forrest Gump 的話來說,迭代數組內容就像一盒巧克力 -- 您永遠不知道將會得到什么。因此有必要使 awk 數組“字符串化”,我們現在就來研究這個問題。
數組下標字符串化
在我的 前一篇文章 中,我演示了 awk 實際上以字符串格式來存儲數字值。雖然 awk 要執行必要的轉換來完成這項工作,但它卻可以使用某些看起來很奇怪的代碼:
|
執行了這段代碼后, c
等于 6
。由于 awk 是“字符串化”的,添加字符串 "1" 和 "2" 在功能上并不比添加數字 1 和 2 難。這兩種情況下,awk 都可以成功執行運算。awk 的“字符串化”性質非??蓯?-- 您可能想要知道如果使用數組的字符串下標會發生什么情況。例如,使用以下代碼:
|
可以預料,這段代碼將打印 "Mr. Whipple"。但如果去掉第二個 "1" 下標中的引號,情況又會怎樣呢?
|
猜想這個代碼片斷的結果比較難。awk 將 myarr["1"]
和 myarr[1]
看作數組的兩個獨立元素,還是它們是指同一個元素?答案是它們指的是同一個元素,awk 將打印 "Mr. Whipple",如同第一個代碼片斷一樣。雖然看上去可能有點怪,但 awk 在幕后卻一直使用數組的字符串下標!
了解了這個奇怪的真相之后,我們中的一些人可能想要執行類似于以下的古怪代碼:
|
這段代碼不僅不會產生錯誤,而且它的功能與前面的示例完全相同,也將打印 "Mr. Whipple"!可以看到,awk 并沒有限制我們使用純整數下標;如果我們愿意,可以使用字符串下標,而且不會產生任何問題。只要我們使用非整數數組下標,如 myarr["name"]
,那么我們就在使用 關聯數組 。從技術上講,如果我們使用字符串下標,awk 的后臺操作并沒有什么不同(因為即便使用“整數”下標,awk 還是會將它看作是字符串)。但是,應該將它們稱作 關聯數組 -- 它聽起來很酷,而且會給您的上司留下印象。字符串化下標是我們的小秘密。;)
數組工具
談到數組時,awk 給予我們許多靈活性??梢允褂米址聵?,而且不需要連續的數字序列下標(例如,可以定義 myarr[1]
和 myarr[1000]
,但不定義其它所有元素)。雖然這些都很有用,但在某些情況下,會產生混淆。幸好,awk 提供了一些實用功能有助于使數組變得更易于管理。
首先,可以刪除數組元素。如果想要刪除數組 fooarray
的元素 1
,輸入:
|
而且,如果想要查看是否存在某個特定數組元素,可以使用特殊的 "in" 布爾運算符,如下所示:
|
下一篇
本文中,我們已經討論了許多基礎知識。下一篇中,我將演示如何使用 awk 的數學運算和字符串函數,以及如何創建您自己的函數,使您完全掌握 awk 知識。我還將指導您創建支票簿結算程序。那時,我會鼓勵您編寫自己的 awk 程序。