AWK
編程範型 | 腳本、過程式、數據驅動[1][2] |
---|---|
設計者 | 阿爾佛雷德·艾侯、彼得·溫伯格以及布萊恩·柯林漢 |
面市時間 | 1977年 |
當前版本 |
|
型態系統 | 無;支持字符串,整數和浮點數,以及正則表達式 |
操作系統 | 跨平台 |
網站 | cm.bell-labs.com/cm/cs/awkbook/index.html |
主要實作產品 | |
awk, GNU Awk, mawk, nawk, MKS AWK, Thompson AWK(編譯器), Awka(編譯器) | |
衍生副語言 | |
「old awk」 oawk 1977, 「new awk」 nawk 1985, 「GNU Awk」 gawk | |
啟發語言 | |
C, Sed, SNOBOL[1][2] | |
影響語言 | |
Tcl, AMPL, Perl |
AWK是一種優良的文本處理工具,Linux及Unix環境中現有的功能最強大的數據處理引擎之一。這種編程及數據操作語言(其名稱得自於它的創始人阿爾佛雷德·艾侯、彼得·溫伯格和布萊恩·柯林漢姓氏的首個字母)的最大功能取決於一個人所擁有的知識。AWK提供了極其強大的功能:可以進行正則表達式的匹配,樣式裝入、流控制、數學運算符、進程控制語句甚至於內置的變量和函數。它具備了一個完整的語言所應具有的幾乎所有精美特性。實際上AWK的確擁有自己的語言:AWK程序設計語言,三位創建者已將它正式定義為「樣式掃描和處理語言」。它允許創建簡短的程序,這些程序讀取輸入文件、為數據排序、處理數據、對輸入執行計算以及生成報表,還有無數其他的功能。gawk是AWK的GNU版本。
最簡單地說,AWK是一種用於處理文本的編程語言工具。AWK在很多方面類似於Unix shell編程語言,儘管AWK具有完全屬於其本身的語法。它的設計思想來源於SNOBOL4、sed、Marc Rochkind設計的有效性語言、語言工具yacc和lex,當然還從C語言中獲取了一些優秀的思想。在最初創造AWK時,其目的是用於文本處理,並且這種語言的基礎是,只要在輸入數據中有模式匹配,就執行一系列指令。該實用工具掃描文件中的每一行,查找與命令行中所給定內容相匹配的模式。如果發現匹配內容,則進行下一個編程步驟。如果找不到匹配內容,則繼續處理下一行。
AWK程序結構
[編輯]AWK是一種處理文本文件的語言。它將文件作為記錄序列處理。在一般情況下,文件內容的每行都是一個記錄。每行內容都會被分割成一系列的域,因此,我們可以認為一行的第一個詞為第一個域,第二個詞為第二個,以此類推。AWK程序是由一些處理特定模式的語句塊構成的。AWK一次可以讀取一個輸入行。對每個輸入行,AWK解釋器會判斷它是否符合程序中出現的各個模式,並執行符合的模式所對應的動作。
——阿爾佛雷德·艾侯,The A-Z of Programming Languages: AWK
AWK程序是由一系列模式--動作對組成的,寫做
pattern { action }
其中pattern
表示AWK在數據中查找的內容,而action
是在找到匹配內容時所執行的一系列命令。輸入行被分成了一些記錄:記錄默認由換行符分割,因此輸入會按照行進行分割。程序使用給定的條件一個個的測試每條記錄,並執行測試通過的條件所對應的action
。pattern
和action
都可以省略不寫。無pattern
默認匹配全部的記錄;而無action
則是打印原始記錄。簡單的AWK表達式之外,pattern
可以是BEGIN
或END
;這兩種條件對應的action
分別是讀取所有的記錄之前和之後。同時,如pattern1, pattern2
的條件表示符合條件pattern1
和pattern2
的記錄及其之間的部分。
除了一般的,C語言風格的算術和邏輯運算符外,AWK允許運算符~
,用來測試正則表達式是否可以與一字符串匹配。作為語法糖,沒有~
運算符的正則表達式會被用來對當前記錄進行測試,相當於/regexp/ ~ $0
。
AWK命令
[編輯]AWK命令即為前文例子中以action
指代的語句。AWK命令可以包括函數調用,變量賦值,計算,及/或各項的組合。標準AWK提供了許多內建函數;其部分實現則可能提供了更多的內建函數。同時,AWK的部分實現支持動態鏈接庫,使得其可以支持更多的函數。
便利起見,下述例子中可能省略大括號({ }
)。
print
命令
[編輯]print 命令用於輸出文本。其輸出的文本總是以"輸出記錄分隔符"(Output record separator, ORS)分割的,其默認值為換行符。該命令的最簡形式為:
print
:會輸出當前記錄的內容。在AWK中,記錄會被分割成「域」,它們可以被分別顯示或使用:print $1
:顯示當前記錄的第1個域print $1, $3
:顯示當前記錄的第1和第3個域,並以預定義的輸出域分隔符(Output field separator, OFS)分隔,其默認值為一個空格符
雖然域的符號($X
)可能類似於某些語言中的變量(例如PHP和perl),但在AWK中,它們指代的是當前記錄的域。另外,$0
是指整個記錄。事實上,命令print
和print $0
的效果是相同的。
print
命令也可以顯示變量、計算、函數調用的結果:
print 3+2
print foobar(3)
print foobar(variable)
print sin(3-2)
其輸出可以重定向到File:
print "expression" > "file name"
或重定向到管道:
print "expression" | "command"
內建變量
[編輯]AWK的內建變量包括域變量,例如$1
、$2
、$3
以及$0
。這些變量給出了記錄中域的內容。
內建變量也包括一些其他變量:
NR
:已輸入記錄的條數。NF
:當前記錄中域的個數。記錄中最後一個域可以以$NF
的方式引用。FILENAME
:當前輸入文件的文件名。FS
:「域分隔符」,用於將輸入記錄分割成域。其默認值為「空白字符」,即空格和制表符。FS
可以替換為其它字符,從而改變域分隔符。RS
:當前的「記錄分隔符」。默認狀態下,輸入的每行都被作為一個記錄,因此默認記錄分隔符是換行符。OFS
:「輸出域分隔符」,即分隔print
命令的參數的符號。其默認值為空格。ORS
:「輸出記錄分隔符」,即每個print
命令之間的符號。其默認值為換行符。OFMT
:「輸出數字格式」(Format for numeric output),其默認值為"%.6g"
。
變量和語法
[編輯]變量名可以是語言關鍵字外的,只包含大小寫拉丁字母,數字和下劃線(_
)的任意字。而操作符+
、-
、*
、/
則分別代表加、減、乘、除。簡單的將兩個變量(或字符串常量)放在一起,則會將二者串接為一個字符串。若二者間至少有一個是常量,則中間可以不加空格;但若二者均為變量,中間必須包括空格。字符串常量是以雙引號("
)分隔的。語句無需以分號結尾。另外,注釋是以#
開頭的。
用戶定義函數
[編輯]函數是以與C語言類似的方式定義的,以關鍵字function
開頭,後面跟函數名稱,參數列表和函數體。
# 示例函数
function add_three (number) {
return number + 3
}
上面的函數可以如此調用:
print add_three(36) # 输出'''39'''
函數可以擁有其私有變量。其私有變量可以寫在參數列表之後,因為這些值會在調用函數時被忽略。通常可以在參數列表中參數和私有變量之間加入一些空格,用以區別「真正的」參數和私有變量。 函數聲明中,函數名和括號間可以有任意空格,但在調用時二者必須緊鄰。
樣例程序
[編輯]Hello World
[編輯]AWK的hello world程序為:
BEGIN { print "Hello, world!" }
注意此處無需寫出exit
語句,因為唯一的模式是BEGIN
。
輸出長度大於80的行
[編輯]輸出長度大於80字符的行。注意模式的默認行為是輸出當前行。
length($0) > 80
輸出單詞計數
[編輯]對輸入中的單詞進行計數,然後輸出行數,單詞數和字符數(類似wc)。
{
w += NF
c += length + 1
}
END { print NR, w, c }
由於沒有提供模式,輸入的全部行都可以匹配該模式,因此對每行都會執行預定操作。注意w+=NF
的含義等同於w = w + NF
。
計算最後一個單詞的和
[編輯]{ s += $NF }
END { print s + 0 }
s
是數值$NF
的累加,$NF
是每條記錄中的最後一個域,NF
(沒有$
)是當前行中域的數量。例如一個域數為4
的行中$NF
相當於$4
。事實上,$
是一個具有最高優先級的一元運算符。(若一行沒有域,則有NF
為0
,而$NF
相當於$0
,是整行,在這種情況下,要麼是空串,要麼只有空白符,因此其數值為0
。)
文件結束時,END
模式得到了匹配,因此可以輸出s
。然而,在沒有輸入行的情況下,s
會沒有值,從而導致沒有輸出。因此,對其加0
可以使AWK在這種情況下對其賦值,從而得到一個數值。這種方法是將字符串強制轉化為數值的慣用法(反之,與空串連接則是將數值強制轉換為字符串的方法,例如s ""
)。如此處理之後,若程序輸入為空文件,可以得到0
作為輸出,而不是一個空行。
匹配輸入行中的範圍
[編輯]$ yes Wikipedia | awk 'NR % 4 == 1, NR % 4 == 3 { printf "%6d %s\n", NR, $0 }' | sed 7q
1 Wikipedia
2 Wikipedia
3 Wikipedia
5 Wikipedia
6 Wikipedia
7 Wikipedia
9 Wikipedia
$
yes命令重複輸入其參數(默認則是輸出y
)。在這裡,我們讓該命令輸出Wikipedia
。動作塊則輸出帶行號的內容。printf
函數可以模擬標準C中的printf
函數,其效果與前述的print
函數類似。而符合模式的行是這樣產生的:NR
是記錄的編號,也就是AWK正在處理行的行號(從1
開始)。%
是取餘數操作符。因此,NR % 4 == 1
對第1
、5
、9
等行為真。類似的,NR % 4 == 3
對3
、7
、11
等行為真。範圍模式在其第一部分匹配(例如對第1
行)之前為假,並在第二部分匹配(例如第3
行)之前為真。然後,再在第二次匹配上其第一部分(例如第5
行)前為假。sed
命令則是用於截取其前7行輸出,防止yes
命令一直運行下去。若head
命令可用的話,這行命令的效果和head -n7
相同。
若範圍模式的第一部分永遠為真,例如設定為1
,可以用來使該範圍從輸入的最開始開始。類似的,若第二部分總是為假,例如0
,則該範圍的結束即為輸入的結束。命令
/^--cut here--$/, 0
會輸出從符合正則表達式^--cut here--$
開始的輸入行,也即從只包含--cut here--
的行開始,直到輸入的結束。
計算詞頻
[編輯]使用關聯數組計算詞頻:
BEGIN {
FS="[^a-zA-Z]+"
}
{
for(i=1; i<=NF; ++i)
words[tolower($i)]++
}
END {
for(i in words)
print i, words[i]
}
BEGIN
塊設定域分隔符為任意非字母字符。值得注意的是,分隔符不僅可以是字符串,也可以是正則表達式。然後,程序對每個輸入行執行相同的操作。在此,對每個域,我們累加其小寫形式出現的次數。最後,在END
塊中,我們輸出單詞及其出現的次數。代碼
for(i in words)
建立了一個遍歷關聯數組中元素的循環,其中,i
會被設為對應的鍵。這一點和多數語言不同,而和Objective-C 2.0中的for...in
語法相似。這樣的語法允許以簡單的方式遍歷數組,從而輸出這些單詞。另外,tolower
函數是One True awk(見下文)的附加函數。
從命令匹配模式
[編輯]這個程序可以以多種不同形式出現。第一個使用Bourne shell腳本來完成大部分工作。這也是最短的一個方法:
$ cat grepinawk
pattern=$1
shift
awk '/'$pattern'/ { print FILENAME ":" $0 }' $*
$
awk命令中的$pattern
並沒有為引號所保護。在這裡,模式可以檢查輸入行($0
)是否與之匹配。FILENAME
變量則包含了當前的文件名。awk沒有顯式的字符串連接操作符;與bash相似,只需簡單的將字符串並列即可。$0
則會輸出原始的輸入行。
也有另外的方法來完成同樣的任務。下面的腳本直接在awk中訪問環境變量。
$ cat grepinawk
pattern=$1
shift
awk '$0 ~ ENVIRON["pattern"] { print FILENAME ":" $0 }' $*
$
這個腳本用到了數組ENVIRON
,一個One True awk中引入的量。其作用類似與POSIX標準中的getenv函數。這個腳本先建立了一個名為pattern
的環境變量,其值為腳本的第一個參數,然後讓awk在其餘的參數所代表的文件內尋找該模式。
~
是用於檢查其兩個操作數是否匹配的運算符;其逆則為!~
。注意正則表達式也屬於普通的字符串,可以儲存於變量中。
下面的方法則採用了在命令行對變量賦值的方法,即在awk的參數中寫入一個變量的值:
$ cat grepinawk
pattern=$1
shift
awk '$0 ~ pattern { print FILENAME ":" $0 }' "pattern=$pattern" $*
$
最後,這種方法是純awk的,無需shell的幫助,也無需知道太多關於awk腳本實現的細節(而在命令行對變量賦值的方法可能與awk的實現相關);但這種方法的腳本有點長:
BEGIN {
pattern = ARGV[1]
for (i = 1; i < ARGC; i++) # 去除第一个参数
ARGV[i] = ARGV[i + 1]
ARGC—if (ARGC == 1) { # 模式是唯一参数,因此强制从标准输入读取
ARGC = 2
ARGV[1] = "-"
}
}
$0 ~ pattern { print FILENAME ":" $0 }
BEGIN
塊的作用不僅僅是提取出第一個參數,也防止第一個參數在BEGIN
塊結束後直接被解釋為輸入文件。ARGC
,輸入參數的數量永遠是不小於1的,因為ARGV[0]
是執行腳本的命令名,通常是"awk"
。另外,ARGV[ARGC]
永遠是空串。對於其中的if
塊,它表明若沒有指定輸入文件,awk會直接讀取標準輸入流(stdin
)。也即
$ awk 'prog'
也可以工作,因為程序中已經將ARGC
置為了2
;若該值為1
,則awk會認為沒有文件需要讀取而直接退出。同時,若需從標準輸入讀取數據,需要將文件名顯式的指定為-
。
自包含的AWK腳本
[編輯]與許多其他的程序語言相似,可以利用「shebang」語法構建自包含的awk腳本。
例如,一個名為hello.awk
,可以輸出「Hello, world!」的UNIX命令可以通過建立內容如下,名為hello.awk
的文件來完成:
#!/usr/bin/awk -f
BEGIN { print "Hello, world!" }
-f
參數告訴awk將該文件作為awk的程序文件,然後即可運行該程序。