Awk是一種便於使用且表達能力強的程序設計語言,可應用於各類計算和數據處理任務。本章是個入門指南,讓你可以儘快地開始編寫你本身的程序。第二章將描述整個語言,而剩下的章節將向你展現如何使用Awk來解決許多不一樣方面的問題。縱觀全書,咱們儘可能選擇了一些對你有用、有趣而且有指導意義的實例。正則表達式
有用的awk程序每每很簡短,僅僅一兩行。假設你有一個名爲 emp.data 的文件,其中包含員工的姓名、薪資(美圓/小時)以及小時數,一個員工一行數據,以下所示:shell
Beth | 4.00 | 0 |
Dan | 3.75 | 0 |
kathy | 4.00 | 10 |
Mark | 5.00 | 20 |
Mary | 5.50 | 22 |
Susie | 4.25 | 18 |
如今你想打印出工做時間超過零小時的員工的姓名和工資(薪資乘以時間)。這種任務對於awk來講就是小菜一碟。輸入這個命令行就能夠了::編程
awk '$3 >0 { print $1, $2 * $3 }' emp.data
你應該會獲得以下輸出:數組
該命令行告訴系統執行引號內的awk程序,從輸入文件 emp.data 獲取程序所需的數據。引號內的部分是個完整的awk程序,包含單個模式-動做語句。模式 $3>0 用於匹配第三列大於0的輸入行,動做:編程語言
{ print $1, $2 * $3 }
打印每一個匹配行的第一個字段以及第二第三字段的乘積。函數
若是你想打印出還沒工做過的員工的姓名,則輸入命令行::工具
awk '$3 == 0 { print $1 }' emp.data
這裏,模式 $3 == 0 匹配第三個字段等於0的行,動做:測試
{ print $1 }
打印該行的第一個字段。atom
當你閱讀本書時,應該嘗試執行與修改示例程序。大多數程序都很簡短,因此你能快速理解awk是如何工做的。在Unix系統上,以上兩個事務在終端裏看起來是這樣的:url
行首的 $ 是系統提示符,也許在你的機器上不同。
AWK程序的結構
讓咱們回頭看一下到底發生了什麼事情。上述的命令行中,引號之間的部分是awk編程語言寫就的程序。本章中的每一個awk程序都是一個或多個模式-動做語句的序列:
awk的基本操做是一行一行地掃描輸入,搜索匹配任意程序中模式的行。詞語「匹配」的準確意義是視具體的模式而言,對於模式 $3 >0 來講,意思是「條件爲真」。
每一個模式依次測試每一個輸入行。對於匹配到行的模式,其對應的動做(也許包含多步)獲得執行,而後讀取下一行並繼續匹配,直到全部的輸入讀取完畢。
上面的程序都是模式與動做的典型示例。:
$3 == 0 { print $1 }
是單個模式-動做語句;對於第三個字段爲0的每行,打印其第一個字段。
模式-動做語句中的模式或動做(但不是同時二者)均可以省略。若是某個模式沒有動做,例如::
$3 == 0
那麼模式匹配到的每一行(即,對於該行,條件爲真)都會被打印出來。該程序會打印 emp.data 文件中第三個字段爲0的兩行
若是有個沒有模式的動做,例如::
{ print $1 }
那麼這種狀況下的動做會打印每一個輸入行的第一列。
因爲模式和動做二者任一都是可選的,因此須要使用大括號包圍動做以區分於其餘模式。
執行AWK程序
執行awk程序的方式有多種。你能夠輸入以下形式的命令行::
awk 'program' input files
從而在每一個指定的輸入文件上執行這個program。例如,你能夠輸入::
awk '$3 == 0 { print $1 }' file1 file2
打印file1和file2文件中第三個字段爲0的每一行的第一個字段。
你能夠省略命令行中的輸入文件,僅輸入::
awk 'program'
這種狀況下,awk會將program應用於你在終端中接着輸入的任意數據行,直到你輸入一個文件結束信號(Unix系統上爲control-d)。以下是Unix系統的一個會話示例:
Beth
Dan
Kathy
加粗的字符是計算機打印的。
這個動做很是便於嘗試awk:輸入你的程序,而後輸入數據,觀察發生了什麼。咱們再次鼓勵你嘗試這些示例並進行改動。
注意命令行中的程序是用單引號包圍着的。這會防止shell解釋程序中 $ 這樣的字符,也容許程序的長度超過一行。
當程序比較短小(幾行的長度)的時候,這種約定會很方便。然而,若是程序較長,將程序寫到一個單獨的文件中會更加方便。假設存在程序 progfile ,輸入命令行::
awk -f progfile optional list of input files
其中 -f 選項指示awk從指定文件中獲取程序。可使用任意文件名替換 progfile 。
錯誤
若是你的awk程序存在錯誤,awk會給你一段診斷信息。例如,若是你打錯了大括號,以下所示::
awk '$3 == 0 [ print $1 }' emp.data
你會獲得以下信息:
「Syntax error」意味着在 >>> <<< 標記的地方檢測到語法錯誤。「Bailing out」意味着沒有試圖恢復。有時你會獲得更多的幫助-關於錯誤是什麼,好比大括號或括弧不匹配。
由於存在句法錯誤,awk就不會嘗試執行這個程序。然而,有些錯誤,直到你的程序被執行纔會檢測出來。例如,若是你試圖用零去除某個數,awk會在這個除法的地方中止處理並報告輸入行的行號以及在程序中的行號(這話是什麼意思?難道輸入行的行號是忽略空行後的行號?)。
這一節接下來的部分包含了一些短小,典型的awk程序,基於操縱上文中提到的 emp.data 文件. 咱們會簡單的解釋程序在作什麼,但這些例子主要是爲了介紹 awk 中常見的一些簡單有用的操做 – 打印字段, 選擇輸入, 轉換數據. 咱們並 沒有展示 awk 程序能作的全部事情, 也並不打算深刻的去探討例子中的一些細節. 但在你讀完這一節以後, 你將可以完成一些簡單的任務, 而且你將發如今閱讀後 面章節的時候會變的容易的多.
咱們一般只會列出程序部分, 而不是整個命令行. 在任何狀況下, 程序均可以用 引號包含起來放到 awk 命令的地一個參數中運行, 就像上文中展現的那樣, 或者 把它放到一個文件中使用 awk 的 -f 參數調用它.
在 awk 中僅僅只有兩種數據類型: 數值 和 字符構成的字符串. emp.data 是 一個包含這類信息的典型文件 – 混合了被空格和(或)製表符分割的數字和詞語.
Awk 程序一次從輸入文件的中讀取一行內容並把它分割成一個個字段, 一般默認 狀況下, 一個字段是一個不包含任何空格或製表符的連續字符序列. 當前輸入的 行中的地一個字段被稱作 $1, 第二個是 $2, 以此類推. 整個行的內容被定 義爲 $0. 每一行的字段數量能夠不一樣.
一般, 咱們要作的僅僅只是打印出每一行中的某些字段, 也許還要作一些計算. 這一節的程序基本上都是這種形式.
打印每一行
若是一個動做沒有任何模式, 這個動做會對全部輸入的行進行操做. print 語 句用來打印(輸出)當前輸入的行, 因此程序
{ print }
會輸出全部輸入的內容到標準輸出. 因爲 $0 表示整行,
{ print $0 }
也會作同樣的事情.
打印特定字段
使用一個 print 語句能夠在同一行中輸出不止一個字段. 下面的程序輸出了每 行輸入中的第一和第三個字段
{ print $1, $3 }
使用 emp.data 做爲輸入, 它將會獲得
Beth 0 Dan 0 Kathy 10 Mark 20 Mary 22 Susie 18
在 print 語句中被逗號分割的表達式, 在默認狀況下他們將會用一個空格分割 來輸出. 每一行 print 生成的內容都會以一個換行符做爲結束. 但這些默認行 爲均可以自定義; 咱們將在第二章中介紹具體的方法.
NF, 字段數量
很顯然你可能會發現你老是須要經過 $1, $2 這樣來指定不一樣的字段, 但任何表 達式均可以使用在$以後來表達一個字段的序號; 表達式會被求值並用於表示字段 序號. Awk會對當前輸入的行有多少個字段進行計數, 而且將當前行的字段數量存 儲在一個內建的稱做 NF 的變量中. 所以, 下面的程序
{ print NF, $1, $NF }
會依次打印出每一行的字段數量, 第一個字段的值, 最後一個字段的值.
計算和打印
你也能夠對字段的值進行計算後再打印出來. 下面的程序
{ print $1, $2 * $3 }
是一個典型的例子. 它會打印出姓名和員工的合計支出(以小時計算):
Beth 0 Dan 0 Kathy 40 Mark 100 Mary 121 Susie 76.5
咱們立刻就會學到怎麼讓這個輸出看起來更漂亮.
打印行號
Awk提供了另外一個內建變量, 叫作 NR, 它會存儲當前已經讀取了多少行的計數. 咱們可使用 NR 和 $0 給 emp.data 的沒一行加上行號:
{ print NR, $0 }
打印的輸出看起來會是這樣:
1 Beth 4.00 0 2 Dan 3.75 0 3 Kathy 4.00 10 4 Mark 5.00 20 5 Mary 5.50 22 6 Susie 4.25 1 8
在輸出中添加內容
你固然也能夠在字段中間或者計算的值中間打印輸出想要的內容:
{ print "total pay for", $1, "is", $2 * $3 }
輸出
total pay for Beth is 0 total pay for Dan is 0 total pay for Kathy is 40 total pay for Mark is 100 total pay for Mary is 121 total pay for Susie is 76.5
在打印語句中, 雙引號內的文字將會在字段和計算的值中插入輸出.
print 語句可用於快速而簡單的輸出。若要嚴格按照你所想的格式化輸出,則須要使用 printf 語句。正如我將在2.4節所見, printf 幾乎能夠產生任何形式的輸出,但在本節中,咱們僅展現其部分功能。
字段排隊
printf 語句的形式以下::
printf(format, value1, value2, ..., valuen)
其中 format 是字符串,包含要逐字打印的文本,穿插着 format 以後的每一個值該如何打印的規格(specification)。一個規格是一個 % 符,後面跟着一些字符,用來控制一個 value 的格式。第一個規格說明如何打印 value1 ,第二個說明如何打印 value2 ,... 。所以,有多少 value 要打印,在 format 中就要有多少個 % 規格。
這裏有個程序使用 printf 打印每位員工的總薪酬::
{ printf("total pay for %s is $%.2f\n", $1, $2 * $3) }
printf 語句中的規格字符串包含兩個 % 規格。第一個是 %s ,說明以字符串的方式打印第一個值 $1 。第二個是 %.2f ,說明以數字的方式打印第二個值 $2*$3 ,並保留小數點後面兩位。規格字符串中其餘東西,包括美圓符號,僅逐字打印。字符串尾部的 \n 表明開始新的一行,使得後續輸出將從下一行開始。以 emp.data 爲輸入,該程序產生:
total pay for Beth is $0.00 total pay for Dan is $0.00 total pay for Kathy is $40.00 total pay for Mark is $100.00 total pay for Mary is $121.00 total pay for Susie is $76.50
printf 不會自動產生空格或者新的行,必須是你本身來建立,因此不要忘了 \n 。
另外一個程序是打印每位員工的姓名與薪酬::
{ printf("%-8s $%6.2f\n", $1, $2 * $3) }
第一個規格 %-8s 將一個姓名以字符串形式在8個字符寬度的字段中左對齊輸出。第二個規格 %6.2f 將薪酬以數字的形式,保留小數點後兩位,在6個字符寬度的字段中輸出。
Beth $ 0.00 Dan $ 0.00 Kathy $ 40.00 Mark $100.00 Mary $121.00 Susie $ 76.50
以後咱們將展現更多的 printf 示例。一切精彩盡在2.4小節。
排序輸出
假設你想打印每位員工的全部數據,包括他或她的薪酬,並以薪酬遞增的方式進行排序輸出。最簡單的方式是使用awk將每位員工的總薪酬置於其記錄以前,而後利用一個排序程序來處理awk的輸出。Unix上,命令行以下:
awk '{ printf("%6.2f %s\n", $2 * $3, $0) }' emp.data | sort
將awk的輸出經過管道傳給 sort 命令,輸出爲:
0.00 Beth 4.00 0 0.00 Dan 3.75 0 40.00 Kathy 4.00 10 76.50 Susie 4.25 18 100.00 Mark 5.00 20 121.00 Mary 5.50 22
Awk的模式適合用於爲進一步的處理從輸入中選擇相關的數據行。因爲不帶動做的模式會打印全部匹配模式的行,因此不少awk程序僅包含一個模式。本節將給出一些有用的模式示例。
經過對比選擇
這個程序使用一個對比模式來選擇每小時賺5美圓或更多的員工記錄,也就是,第二個字段大於等於5的行::
$2 >= 5
從 emp.data 中選出這些行::
Mark 5.00 20 Mary 5.50 22
經過計算選擇
程序
$2 * $3 > 50 { printf("$%.2f for %s\n", $2 * $3, $1) }
打印出總薪資超過50美圓的員工的薪酬。
經過文本內容選擇
除了數值測試,你還能夠選擇包含特定單詞或短語的輸入行。這個程序會打印全部第一個字段爲 Susie 的行::
$1 == "Susie"
操做符 == 用於測試相等性。你也可使用稱爲 正則表達式 的模式查找包含任意字母組合,單詞或短語的文本。這個程序打印任意位置包含 Susie 的行::
/Susie/
輸出爲這一行::
Susie 4.25 18
正則表達式可用於指定複雜的多的模式;2.1節將會有全面的論述。
模式組合
可使用括號和邏輯操做符與 && , 或 || , 以及非 ! 對模式進行組合。程序:
$2 >= 4 || $3 >= 20
會打印 $2 (第二個字段) 大於等於 4 或者 $3 (第三個字段) 大於等於 20 的行::
Beth 4.00 0 kathy 4.00 10 Mark 5.00 20 Mary 5.50 22 Susie 4.25 18
兩個條件都知足的行僅打印一次。與以下包含兩個模式程序相比::
$2 >= 4 $3 >= 20
若是某個輸入行兩個條件都知足,這個程序會打印它兩遍::
Beth 4.00 0 Kathy 4.00 10 Mark 5.00 20 Mark 5.00 20 Mary 5.50 22 Mary 5.50 22 Susie 4.25 18
注意以下程序:
!($2 < 4 && $3 < 20)
會打印極不知足 $2 小於4也不知足 $3 小於20的行;這個條件與上面第一個模式組合等價,雖然也許可讀性差了點。
數據驗證
實際的數據中老是會存在錯誤的。在數據驗證-檢查數據的值是否合理以及格式是否正確-方面,Awk是個優秀的工具。
數據驗證本質上是否認的:不是打印具有指望屬性的行,而是打印可疑的行。以下程序使用對比模式 將5個數據合理性測試應用於 emp.data 的每一行::
NF != 3 { print $0, "number of fields is not equal to 3" } $2 < 3.35 { print $0, "rate is below minimum wage" } $2 > 10 { print $0, "rate exceeds $10 per hour" } $3 < 0 { print $0, "negative hours worked" } $3 > 60 { print $0, "too many hours worked" }
若是沒有錯誤,則沒有輸出。
BEGIN與END
特殊模式 BEGIN 用於匹配第一個輸入文件的第一行以前的位置, END 則用於匹配處理過的最後一個文件的最後一行以後的位置。這個程序使用 BEGIN 來輸出一個標題::
BEGIN { print "Name RATE HOURS"; print ""} { print }
輸出爲::
NAME RATE HOURS Beth 4.00 0 Dan 3.75 0 Kathy 4.00 10 Mark 5.00 20 Mary 5.50 22 Susie 4.25 18
程序的動做部分你能夠在一行上放多個語句,不過要使用分號進行分隔。注意 普通的 print 是打印當前輸入行,與之不一樣的是 print 「」 會打印一個空行。
一個動做就是一個以新行或者分號分隔的語句序列。你已經見過一些其動做僅是單個 print 語句的例子。本節將提供一些執行簡單的數值以及字符串計算的語句示例。在這些語句中,你不只可使用像 NF 這樣的內置變量,還能夠建立本身的變量用於計算、存儲數據諸如此類的操做。awk中,用戶建立的變量不須要聲明。
計數
這個程序使用一個變量 emp 來統計工做超過15個小時的員工的數目::
$3 > 15 { emp = emp + 1 } END { print emp, "employees worked more than 15 hours" }
對於第三個字段超過15的每行, emp 的前一個值加1。以 emp.data 爲輸入,該程序產生::
3 employees worked more than 15 hours
用做數字的awk變量的默認初始值爲0,因此咱們不須要初始化 emp 。
求和與平均值
爲計算員工的數目,咱們可使用內置變量 NR ,它保存着到目前位置讀取的行數;在全部輸入的結尾它的值就是所讀的全部行數。
END { print NR, "employees" }
輸出爲::
6 employees
以下是一個使用 NR 來計算薪酬均值的程序::
{ pay = pay + $2 * $3 } END { print NR, "employees" print "total pay is", pay print "average pay is", pay/NR }
第一個動做累計全部員工的總薪酬。 END 動做打印出
6 employees total pay is 337.5 average pay is 56.25
很明顯, printf 可用來產生更簡潔的輸出。而且該程序也有個潛在的錯誤:在某種不太可能發生的狀況下, NR 等於0,那麼程序會試圖執行零除,從而產生錯誤信息。
處理文本
awk的優點之一是能像大多數語言處理數字同樣方便地處理字符串。awk變量能夠保存數字也能夠保存字符串。這個程序會找出時薪最高的員工::
$2 > maxrate { maxrate = $2; maxemp = $1 } END { print "highest hourly rate:", maxrate, "for", maxemp }
輸出
highest hourly rate: 5.50 for Mary
這個程序中,變量 maxrate 保存着一個數值,而變量 maxemp 則是保存着一個字符串。(若是有幾個員工都有着相同的最大時薪,該程序則只找出第一個。)
字符串鏈接
能夠合併老字符串來建立新字符串。這種操做稱爲 鏈接(concatenation) 。程序
{ names = names $1 " "} END { print names }
經過將每一個姓名和一個空格附加到變量 names 的前一個值, 來將全部員工的姓名收集進單個字符串中。最後 END 動做打印出 names 的值::
Beth Dan Kathy Mark Mary Susie
awk程序中,鏈接操做的表現形式是將字符串值一個接一個地寫出來。對於每一個輸入行,程序的第一個語句先鏈接三個字符串: names 的前一個值、當前行的第一個字段以及一個空格,而後將獲得的字符串賦值給 names 。所以,讀取全部的輸入行以後, names 就是個字符串,包含全部員工的姓名,每一個姓名後面跟着一個空格。用於保存字符串的變量的默認初始值是空字符串(也就是說該字符串包含零個字符),所以這個程序中的 names 不須要顯式初始化。
打印最後一個輸入行
雖然在 END 動做中 NR 還保留着它的值,但 $0 沒有。程序
{ last = $0 } END { print last }
是打印最後一個輸入行的一種方式::
Susie 4.25 18
內置函數
咱們已看到awk提供了內置變量來保存某些頻繁使用的數量,好比:字段的數量和輸入行的數量。相似地,也有內置函數用來計算其餘有用的數值。除了平方根、對數、隨機數諸如此類的算術函數,也有操做文本的函數。其中之一是 length ,計算一個字符串中的字符數量。例如,這個程序會計算每一個人的姓名的長度::
{ print $1, length($1) }
結果::
Beth 4 Dan 3 Kathy 5 Mark 4 Mary 4 Susie 5
行、單詞以及字符的計數
這個程序使用了 length 、 NF 、以及 NR 來統計輸入中行、單詞以及字符的數量。爲了簡便,咱們將每一個字段看做一個單詞。
{ nc = nc + length($0) + 1 nw = nw + NF } END { print NR, "lines,", nw, "words,", nc, "characters" }
文件 emp.data 有:
6 lines, 18 words, 77 characters
$0 並不包含每一個輸入行的末尾的換行符,因此咱們要另外加個1。
Awk爲選擇提供了一個 if-else 語句,以及爲循環提供了幾個語句,因此都效仿C語言中對應的控制語句。它們僅能夠在動做中使用。
if-else語句
以下程序將計算時薪超過6美圓的員工的總薪酬與平均薪酬。它使用一個 if 來防範計算平均薪酬時的零除問題。
$2 > 6 { n = n + 1; pay = pay + $2 * $3 } END { if (n > 0) print n, "employees, total pay is", pay, "average pay is", pay/n else print "no employees are paid more than $6/hour" }
emp.data 的輸出是::
no employees are paid more than $6/hour
if-else 語句中,if 後的條件會被計算。若是爲真,執行第一個 print 語句。不然,執行第二個 print 語句。注意咱們可使用一個逗號將一個長語句截斷爲多行來書寫。
while語句
一個 while 語句有一個條件和一個執行體。條件爲真時執行體中的語句會被重複執行。這個程序使用公式 value=amount(1+rate)yearsvalue=amount(1+rate)years
來演示以特定的利率投資必定量的錢,其數值是如何隨着年數增加的。
# interest1 - 計算複利
# 輸入: 錢數 利率 年數 # 輸出: 複利值 { i = 1 while (i <= $3) { printf("\t%.2f\n", $1 * (1 + $2) ^ i) i = i + 1 } }
條件是 while 後括弧包圍的表達式;循環體是條件後大括號包圍的兩個表達式。 printf 規格字符串中的 \t 表明製表符; ^ 是指數操做符。從 # 開始到行尾的文本是註釋,會被awk忽略,但能幫助程序的讀者理解程序作的事情。
你能夠爲這程序輸入三個一組的數字,看看不同的錢數、利率、以及年數會產生什麼。例如,以下事務演示了1000美圓,利率爲6%與12%,5年的複利分別是如何增加的::
$ awk -f interest1 1000 .06 5 1060.00 1123.60 1191.02 1262.48 1338.23 1000 .12 5 1120.00 1254.40 1404.93 1573.52 1762.34
for語句
另外一個語句, for ,將大多數循環都包含的初始化、測試、以及自增壓縮成一行。以下是以前利息計算的 for 版本::
# interest1 - 計算複利
# 輸入: 錢數 利率 年數 # 輸出: 每一年末的複利 { for (i = 1; i <= $3; i = i + 1) printf("\t%.2f\n", $1 * (1 + $2) ^ i) }
初始化 i = 1 只執行一次。接下來,測試條件 i <= $3 ;若是爲真,則執行循環體的 printf 語句。循環體執行結束後執行自增 i = i + 1 ,接着由另外一次條件測試開始下一個循環迭代。代碼更加緊湊,而且因爲循環體僅是一條語句,因此不須要大括號來包圍它。
awk爲存儲一組相關的值提供了數組。雖然數組給予了awk很強的能力,但在這裏咱們僅展現一個簡單的例子。以下程序將按行逆序打印輸入。第一個動做將輸入行存爲數組 line 的連續元素;即第一行放在 line[1] ,第二行放在 line[2] , 依次繼續。 END 動做使用一個 while 語句從後往前打印數組中的輸入行::
# 反轉 - 按行逆序打印輸入
{ line[NR] = $0 } # 記下每一個輸入行 END { i = NR # 逆序打印 while (i > 0) { print line[i] i = i - 1 } }
以 emp.data 爲輸入,輸出爲
Susie 4.25 18 Mary 5.50 22 Mark 5.00 20 Kathy 4.00 10 Dan 3.75 0 Beth 4.00 0
以下是使用 for 語句實現的相同示例::
# 反轉 - 按行逆序打印輸入
{ line[NR] = $0 } # 記下每一個輸入行 END { for (i = NR; i > 0; i = i - 1) print line[i] }