awk線上例項入門教程 第二部分

helloxchen發表於2010-10-21
awk線上例項入門教程 第二部分
——記錄、迴圈和陣列

多行記錄

awk 是一種用於讀取和處理結構化資料(如系統的 /etc/passwd 檔案)的極佳工具。/etc/passwd 是 UNIX 使用者資料庫,並且是用冒號定界的文字檔案,它包含許多重要資訊,包括所有現有使用者帳戶和使用者標識,以及其它資訊。在第一部分中,演示了 awk 如何輕鬆地分析這個檔案。我們只須將 FS(欄位分隔符)變數設定成 ":"。

正確設定了 FS 變數之後,就可以將 awk 配置成分析幾乎任何型別的結構化資料,只要這些資料是每行一個記錄。然而,如果要分析佔據多行的記錄,僅僅依靠設定 FS 是不夠的。在這些情況下,我們還需要修改 RS 記錄分隔符變數。RS 變數告訴 awk 當前記錄什麼時候結束,新記錄什麼時候開始。

譬如,讓我們討論一下如何完成處理“聯邦證人保護計劃”所涉及人員的地址列表的任務:
Jimmy the Weasel
100 Pleasant Drive
San Francisco, CA 12345
Big Tony
200 Incognito Ave.
Suburbia, WA 67890


理論上,我們希望 awk 將每 3 行看作是一個獨立的記錄,而不是三個獨立的記錄。如果 awk 將地址的第一行看作是第一個欄位 ($1),街道地址看作是第二個欄位 ($2),城市、州和郵政編碼看作是第三個欄位 $3,那麼這個程式碼就會變得很簡單。以下就是我們想要得到的程式碼:
  1. BEGIN {
  2. FS="n"
  3. RS=""
  4. }
複製程式碼
在上面這段程式碼中,將 FS 設定成 "n" 告訴 awk 每個欄位都佔據一行。透過將 RS 設定成 "",還會告訴 awk 每個地址記錄都由空白行分隔。一旦 awk 知道是如何格式化輸入的,它就可以為我們執行所有分析工作,指令碼的其餘部分很簡單。讓我們研究一個完整的指令碼,它將分析這個地址列表,並將每個記錄列印在一行上,用逗號分隔每個欄位。

address.awk
  1. BEGIN {
  2. FS="n"
  3. RS=""
  4. }
  5. {
  6. print $1 ", " $2 ", " $3
  7. }
複製程式碼
如果這個指令碼儲存為 address.awk,地址資料儲存在檔案 address.txt 中,可以透過輸入 "awk -f address.awk address.txt" 來執行這個指令碼。此程式碼將產生以下輸出:

Jimmy the Weasel, 100 Pleasant Drive, San Francisco, CA 12345
Big Tony, 200 Incognito Ave., Suburbia, WA 67890


OFS 和 ORS

在 address.awk 的 print 語句中,可以看到 awk 會連線(合併)一行中彼此相鄰的字串。我們使用此功能在同一行上的三個欄位之間插入一個逗號和空格 (", ")。這個方法雖然有用,但比較難看。與其在欄位間插入 ", " 字串,倒不如讓透過設定一個特殊 awk 變數 OFS,讓 awk 完成這件事。請參考下面這個程式碼片斷。
  1. print "Hello", "there", "Jim!"
複製程式碼
這行程式碼中的逗號並不是實際文字字串的一部分。事實上,它們告訴 awk "Hello"、"there" 和 "Jim!" 是單獨的欄位,並且應該在每個字串之間列印 OFS 變數。預設情況下,awk 產生以下輸出:
Hello there Jim!



這是預設情況下的輸出結果,OFS 被設定成 " ",單個空格。不過,我們可以方便地重新定義 OFS,這樣 awk 將插入我們中意的欄位分隔符。以下是原始 address.awk 程式的修訂版,它使用 OFS 來輸出那些中間的 ", " 字串:

address.awk 的修訂版
  1. BEGIN {
  2. FS="n"
  3. RS=""
  4. OFS=", "
  5. }
  6. {
  7. print $1, $2, $3
  8. }
複製程式碼
awk 還有一個特殊變數 ORS,全稱是“輸出記錄分隔符”。透過設定預設為換行 ("n") 的 OFS,我們可以控制在 print 語句結尾自動列印的字元。預設 ORS 值會使 awk 在新行中輸出每個新的 print 語句。如果想使輸出的間隔翻倍,可以將 ORS 設定成 "nn"。或者,如果想要用單個空格分隔記錄(而不換行),將 ORS 設定成 ""。

將多行轉換成用 tab 分隔的格式

假設我們編寫了一個指令碼,它將地址列表轉換成每個記錄一行,且用 tab 定界的格式,以便匯入電子表格。使用稍加修改的 address.awk 之後,就可以清楚地看到這個程式只適合於三行的地址。如果 awk 遇到以下地址,將丟掉第四行,並且不列印該行:
Cousin Vinnie
Vinnie's Auto Shop
300 City Alley
Sosueme, OR 76543


要處理這種情況,程式碼最好考慮每個欄位的記錄數量,並依次列印每個記錄。現在,程式碼只列印地址的前三個欄位。以下就是我們想要的一些程式碼:

適合具有任意多欄位的地址的 address.awk 版本
  1. BEGIN {
  2. FS="n"
  3. RS=""
  4. ORS=""
  5. }
  6. {
  7. x=1
  8. while ( x
  9. print $x "t"
  10. x++
  11. }
  12. print $NF "n"
  13. }
複製程式碼
首先,將欄位分隔符 FS 設定成 "n",將記錄分隔符 RS 設定成 "",這樣 awk 可以象以前一樣正確分析多行地址。然後,將輸出記錄分隔符 ORS 設定成 "",它將使 print 語句在每個呼叫結尾 不 輸出新行。這意味著如果希望任何文字從新的一行開始,那麼需要明確寫入 print "n" 。

在主程式碼塊中,建立了一個變數 x 來儲存正在處理的當前欄位的編號。起初,它被設定成 1。然後,我們使用 while 迴圈(一種 awk 迴圈結構,等同於 C 語言中的 while 迴圈),對於所有記錄(最後一個記錄除外)重複列印記錄和 tab 字元。最後,列印最後一個記錄和換行;此外,由於將 ORS 設定成 "",print 將不輸出換行。程式輸出如下,這正是我們所期望的:

我們想要的輸出。不算漂亮,但用 tab 定界,以便於匯入電子表格

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



迴圈結構

我們已經看到了 awk 的 while 迴圈結構,它等同於相應的 C 語言 while 迴圈。awk 還有 "do...while" 迴圈,它在程式碼塊結尾處對條件求值,而不象標準 while 迴圈那樣在開始處求值。它類似於其它語言中的 "repeat...until" 迴圈。以下是一個示例:

do...while 示例

{
count=1
do {
print "I get printed at least once no matter what"
} while ( count != 1 )
}


與一般的 while 迴圈不同,由於在程式碼塊之後對條件求值,"do...while" 迴圈永遠都至少執行一次。換句話說,當第一次遇到普通 while 迴圈時,如果條件為假,將永遠不執行該迴圈。

for 迴圈
awk 允許建立 for 迴圈,它就象 while 迴圈,也等同於 C 語言的 for 迴圈:
  1. for ( initial assignment; comparison; increment ) {
  2. code block
  3. }
複製程式碼
以下是一個簡短示例:
  1. for ( x = 1; x <= 4; x++ ) {
  2. print "iteration",x
  3. }
複製程式碼
此段程式碼將列印:

iteration 1
iteration 2
iteration 3
iteration 4


break 和 continue

此外,如同 C 語言一樣,awk 提供了 break 和 continue 語句。使用這些語句可以更好地控制 awk 的迴圈結構。以下是迫切需要 break 語句的程式碼片斷:

while 死迴圈
  1. while (1) {
  2. print "forever and ever..."
  3. }
複製程式碼
while 死迴圈 1 永遠代表是真,這個 while 迴圈將永遠執行下去。以下是一個只執行十次的迴圈:

break 語句示例
  1. x=1
  2. while(1) {
  3. print "iteration",x
  4. if ( x == 10 ) {
  5. break
  6. }
  7. x++
  8. }
複製程式碼
這裡,break 語句用於“逃出”最深層的迴圈。"break" 使迴圈立即終止,並繼續執行迴圈程式碼塊後面的語句。

continue 語句補充了 break,其作用如下:
  1. x=1
  2. while (1) {
  3. if ( x == 4 ) {
  4. x++
  5. continue
  6. }
  7. print "iteration",x
  8. if ( x > 20 ) {
  9. break
  10. }
  11. x++
  12. }
複製程式碼
這段程式碼列印 "iteration 1" 到 "iteration 21","iteration 4" 除外。如果迭代等於 4,則增加 x 並呼叫 continue 語句,該語句立即使 awk 開始執行下一個迴圈迭代,而不執行程式碼塊的其餘部分。如同 break 一樣,continue 語句適合各種 awk 迭代迴圈。在 for 迴圈主體中使用時,continue 將使迴圈控制變數自動增加。以下是一個等價迴圈:
  1. for ( x=1; x<=21; x++ ) {
  2. if ( x == 4 ) {
  3. continue
  4. }
  5. print "iteration",x
  6. }
複製程式碼
在 while 迴圈中時,在呼叫 continue 之前沒有必要增加 x ,因為 for 迴圈會自動增加 x 。

陣列

如果您知道 awk 可以使用陣列,您一定會感到高興。然而,在 awk 中,陣列下標通常從 1 開始,而不是 0:
  1. myarray[1]="jim"
  2. myarray[2]=456
複製程式碼
awk 遇到第一個賦值語句時,它將建立 myarray ,並將元素 myarray[1] 設定成 "jim"。執行了第二個賦值語句後,陣列就有兩個元素了。

陣列迭代

定義之後,awk 有一個便利的機制來迭代陣列元素,如下所示:
  1. for ( x in myarray ) {
  2. print myarray[x]
  3. }
複製程式碼
這段程式碼將列印陣列 myarray 中的每一個元素。當對於 for 使用這種特殊的 "in" 形式時,awk 將 myarray 的每個現有下標依次賦值給 x (迴圈控制變數),每次賦值以後都迴圈一次迴圈程式碼。雖然這是一個非常方便的 awk 功能,但它有一個缺點 -- 當 awk 在陣列下標之間輪轉時,它不會依照任何特定的順序。那就意味著我們不能知道以上程式碼的輸出是:
jim
456

還是
456
jim


套用 Forrest Gump 的話來說,迭代陣列內容就像一盒巧克力 -- 您永遠不知道將會得到什麼。因此有必要使 awk 陣列“字串化”,我們現在就來研究這個問題。

陣列下標字串化

在我的 前一篇文章 中,我演示了 awk 實際上以字串格式來儲存數字值。雖然 awk 要執行必要的轉換來完成這項工作,但它卻可以使用某些看起來很奇怪的程式碼:
  1. a="1"
  2. b="2"
  3. c=a+b+3
複製程式碼
執行了這段程式碼後, c 等於 6 。由於 awk 是“字串化”的,新增字串 "1" 和 "2" 在功能上並不比新增數字 1 和 2 難。這兩種情況下,awk 都可以成功執行運算。awk 的“字串化”性質非常可愛 -- 您可能想要知道如果使用陣列的字串下標會發生什麼情況。例如,使用以下程式碼:
  1. myarr["1"]="Mr. Whipple"
  2. print myarr["1"]
複製程式碼
可以預料,這段程式碼將列印 "Mr. Whipple"。但如果去掉第二個 "1" 下標中的引號,情況又會怎樣呢?
  1. myarr["1"]="Mr. Whipple"
  2. print myarr[1]
複製程式碼
猜想這個程式碼片斷的結果比較難。awk 將 myarr["1"] 和 myarr[1] 看作陣列的兩個獨立元素,還是它們是指同一個元素?答案是它們指的是同一個元素,awk 將列印 "Mr. Whipple",如同第一個程式碼片斷一樣。雖然看上去可能有點怪,但 awk 在幕後卻一直使用陣列的字串下標!

瞭解了這個奇怪的真相之後,我們中的一些人可能想要執行類似於以下的古怪程式碼:
  1. myarr["name"]="Mr. Whipple"
  2. print myarr["name"]
複製程式碼
這段程式碼不僅不會產生錯誤,而且它的功能與前面的示例完全相同,也將列印 "Mr. Whipple"!可以看到,awk 並沒有限制我們使用純整數下標;如果我們願意,可以使用字串下標,而且不會產生任何問題。只要我們使用非整數陣列下標,如 myarr["name"] ,那麼我們就在使用 關聯陣列 。從技術上講,如果我們使用字串下標,awk 的後臺操作並沒有什麼不同(因為即便使用“整數”下標,awk 還是會將它看作是字串)。但是,應該將它們稱作 關聯陣列 -- 它聽起來很酷,而且會給您的上司留下印象。字串化下標是我們的小秘密。;)

陣列工具

談到陣列時,awk 給予我們許多靈活性。可以使用字串下標,而且不需要連續的數字序列下標(例如,可以定義 myarr[1] 和 myarr[1000] ,但不定義其它所有元素)。雖然這些都很有用,但在某些情況下,會產生混淆。幸好,awk 提供了一些實用功能有助於使陣列變得更易於管理。

首先,可以刪除陣列元素。如果想要刪除陣列 fooarray 的元素 1 ,輸入:
  1. delete fooarray[1]
複製程式碼
而且,如果想要檢視是否存在某個特定陣列元素,可以使用特殊的 "in" 布林運算子,如下所示:

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/24790158/viewspace-1040123/,如需轉載,請註明出處,否則將追究法律責任。

awk線上例項入門教程 第二部分
請登入後發表評論 登入
全部評論

相關文章