Awk 入門/Awk 命令列示例
在命令列中使用 Awk 對文字檔案執行簡單操作非常容易。假設我有一個名為 "coins.txt" 的檔案,其中描述了我的硬幣收藏。該檔案中的每一行都包含以下資訊
- 金屬
- 重量(盎司)
- 鑄造日期
- 原產國
- 描述
該檔案的內容如下
gold 1 1986 USA American Eagle gold 1 1908 Austria-Hungary Franz Josef 100 Korona silver 10 1981 USA ingot gold 1 1984 Switzerland ingot gold 1 1979 RSA Krugerrand gold 0.5 1981 RSA Krugerrand gold 0.1 1986 PRC Panda silver 1 1986 USA Liberty dollar gold 0.25 1986 USA Liberty 5-dollar piece silver 0.5 1986 USA Liberty 50-cent piece silver 1 1987 USA Constitution dollar gold 0.25 1987 USA Constitution 5-dollar piece gold 1 1988 Canada Maple Leaf
然後,我可以呼叫 Awk 列出所有金質硬幣,如下所示
awk '/gold/' coins.txt
這告訴 Awk 在檔案中搜索包含字串 "gold" 的文字行,並將其打印出來。結果是
gold 1 1986 USA American Eagle gold 1 1908 Austria-Hungary Franz Josef 100 Korona gold 1 1984 Switzerland ingot gold 1 1979 RSA Krugerrand gold 0.5 1981 RSA Krugerrand gold 0.1 1986 PRC Panda gold 0.25 1986 USA Liberty 5-dollar piece gold 0.25 1987 USA Constitution 5-dollar piece gold 1 1988 Canada Maple Leaf
一個評論家可能會說,這很好,但是任何 "grep" 或 "find" 工具都可以做到同樣的事情。沒錯,但是 Awk 能夠做到更多。例如,假設我只想列印描述欄位,而省略所有其他文字。那麼我可以更改對 Awk 的呼叫,如下所示
awk '/gold/ {print $5,$6,$7,$8}' coins.txt
這將產生
American Eagle Franz Josef 100 Korona ingot Krugerrand Krugerrand Panda Liberty 5-dollar piece Constitution 5-dollar piece Maple Leaf
- 最簡單的 Awk 程式
此示例演示了 Awk 程式最簡單的通用形式
awk search pattern { program actions }
Awk 按行掃描輸入檔案,尋找搜尋模式。對於找到的每一行,Awk 都會執行指定的動作。在這個示例中,動作被指定為
{print $5,$6,$7,$8}
print 語句的用途很明顯。$5、$6、$7 和 $8 是欄位或“欄位變數”,它們按數字順序儲存每行文字中的詞語。例如,$1 儲存該行中的第一個詞,$2 儲存第二個詞,依此類推。預設情況下,“詞語”或記錄被定義為任何由空格分隔的列印字元序列。
基於 "coins.txt" 的結構(見上文),欄位變數與檔案中每行文字的匹配方式如下
metal: $1 weight: $2 date: $3 country: $4 description: $5 through $8
本示例中的程式動作列印包含描述的欄位。檔案中的描述欄位實際上可能包含一個到四個欄位,但這並不重要,因為 "print" 只是忽略任何未定義的欄位。細心的讀者會注意到 "coins.txt" 檔案井然有序,所以唯一包含多個欄位的資訊位於行的末尾。透過更改欄位分隔符(後面會解釋)可以克服這個限制。
Awk 的預設程式動作是列印整行,這正是 "print" 在沒有引數呼叫時所做的。這意味著這三個示例是相同的
awk '/gold/'
awk '/gold/ {print}'
awk '/gold/ {print $0}'
請注意,Awk 將欄位變數 $0 識別為表示整行。這有點多餘,但它確實有使其操作更明顯的優點。
現在假設我想列出所有在 1980 年之前鑄造的硬幣。我呼叫 Awk 如下所示
awk '{if ($3 < 1980) print $3, " ",$5,$6,$7,$8}' coins.txt
這將產生
1908 Franz Josef 100 Korona 1979 Krugerrand
這個新的示例添加了一些新的概念
- 列印行
- 如果未指定搜尋模式,Awk 將匹配輸入檔案中的所有行,並在每一行上執行操作。
print語句可以透過將文字包含在引號中並將其新增到引數列表中來顯示自定義文字(在本例中為四個空格)。if語句用於檢查特定條件,只有在該條件為真時才會執行print語句。
然而,這裡有一個微妙的問題。在大多數計算機語言中,字串是字串,數字是數字。有一些操作是針對它們中的每一種的,必須使用轉換函式將一種專門轉換為另一種。你不會連線數字,也不會對字串執行算術運算。
另一方面,Awk 並沒有在字串和數字之間進行嚴格區分。用計算機科學術語來說,它不是一種“強型別”語言。Awk 中的所有資料都被視為字串,但如果該字串恰好表示一個數字,則可以對其執行數值運算。因此,我們可以對日期欄位進行算術比較。
下一個示例打印出收藏中硬幣的數量
awk 'END {print NR,"coins"}' coins.txt
這將產生
13 coins
本示例中第一個新項是 END 語句。為了解釋這一點,我必須擴充套件 Awk 程式的通用形式。
- Awk 程式
每個 Awk 程式都遵循以下格式(每個部分都是可選的)
awk 'BEGIN { initializations } search pattern 1 { program actions } search pattern 2 { program actions } ... END { final actions }' input file
BEGIN 子句在 Awk 開始掃描輸入檔案之前執行任何所需的初始化操作。Awk 程式的後續主體由一系列搜尋模式組成,每個模式都有自己的程式動作。Awk 掃描輸入檔案中的每一行以匹配每個搜尋模式,並對找到的每個字串執行相應的動作。掃描完檔案後,可以使用 END 子句執行所需的任何最終動作。
因此,這個示例不會對輸入行本身執行任何處理。它所做的只是掃描檔案並執行一個最終動作:列印檔案中的行數,該行數由 NR 變數給出。NR 代表“記錄數”。NR 是 Awk 的“預定義”變數之一。還有其他變數,例如變數 NF 給出了該行中的欄位數,但詳細解釋要等到以後。
假設黃金的當前價格為每盎司 425 美元,我想計算出硬幣收藏中金質硬幣的總價值(近似值)。我呼叫 Awk 如下所示
awk '/gold/ {ounces += $2} END {print "value = $" 425*ounces}' coins.txt
這將產生
value = $2592.5
在本示例中,ounces 是我自己定義的變數,或“使用者定義”變數。在 Awk 中,幾乎任何字元序列都可以用作變數名,只要該名稱不與 Awk 中具有特定含義的某些字串衝突,例如 print 或 NR 或 END。無需宣告變數或初始化變數。作為字串值處理的變數將被初始化為“空字串”,這意味著如果你嘗試列印它,將沒有任何內容。作為數值處理的變數將被初始化為零。
因此,程式動作
{ounces += $2}
將每行匹配的硬幣的重量累加到變數 ounces 中。那些用 C 程式設計的人應該熟悉 += 運算子。那些不熟悉的人可以放心,這只是以下語句的簡寫形式
{ounces = ounces + $2}
最終的動作是計算並列印黃金的價值
END {print "value = $" 425*ounces}
這裡唯一有趣的是兩個列印引數——文字 value = $ 和表示式 425*ounces——用空格分隔,而不是用逗號分隔。這將兩個引數在輸出時連線在一起,沒有任何空格。
- 嘗試修改上面的某個程式來分別計算並顯示金銀的總量(以盎司為單位),但使用同一個程式。你將不得不使用兩對模式/動作。
- 編寫一個 Awk 程式,查詢在美國鑄造的所有硬幣的平均重量。
- 編寫一個 Awk 程式,在每行文字之前重新列印其輸入內容,並在其前面加上行號。
在下一章中,我們將學習如何編寫長度超過一行的 Awk 程式。