AWK

維基百科,自由的百科全書
跳至導覽 跳至搜尋
AWK
編程範型 腳本過程式事件驅動
設計者 阿爾佛雷德·艾侯彼得·溫伯格以及布萊恩·柯林漢
面市時間 1977
穩定版本
IEEE Std 1003.1-2004 (POSIX) / 1985
型態系統 無;支持字符串,整數和浮點數,以及正則表達式
作業系統 跨平台
網站 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, SNOBOL4, Bourne shell
影響語言
Tcl, AMPL, Perl

AWK是一種優良的文本處理工具,LinuxUnix環境中現有的功能最強大的數據處理引擎之一。這種編程及數據操作語言(其名稱得自於它的創始人阿爾佛雷德·艾侯彼得·溫伯格布萊恩·柯林漢姓氏的首個字母)的最大功能取決於一個人所擁有的知識。AWK提供了極其強大的功能:可以進行正則表達式的匹配,樣式裝入、流控制、數學運算符、進程控制語句甚至於內置的變量和函數。它具備了一個完整的語言所應具有的幾乎所有精美特性。實際上AWK的確擁有自己的語言:AWK程序設計語言,三位創建者已將它正式定義為「樣式掃描和處理語言」。它允許創建簡短的程序,這些程序讀取輸入文件、為數據排序、處理數據、對輸入執行計算以及生成報表,還有無數其他的功能。gawk是AWK的GNU版本。

最簡單地說,AWK是一種用於處理文本的程式語言工具。AWK在很多方面類似於Unix shell程式語言,儘管AWK具有完全屬於其本身的語法。它的設計思想來源於SNOBOL4sed、Marc Rochkind設計的有效性語言、語言工具yacclex,當然還從C語言中獲取了一些優秀的思想。在最初創造AWK時,其目的是用於文本處理,並且這種語言的基礎是,只要在輸入數據中有模式匹配,就執行一系列指令。該實用工具掃描文件中的每一行,查找與命令行中所給定內容相匹配的模式。如果發現匹配內容,則進行下一個編程步驟。如果找不到匹配內容,則繼續處理下一行。

AWK程序結構[編輯]

AWK是一種處理文本文件的語言。它將文件作為記錄序列處理。在一般情況下,文件內容的每行都是一個記錄。每行內容都會被分割成一系列的域,因此,我們可以認為一行的第一個詞為第一個域,第二個詞為第二個,以此類推。AWK程序是由一些處理特定模式的語句塊構成的。AWK一次可以讀取一個輸入行。對每個輸入行,AWK解釋器會判斷它是否符合程序中出現的各個模式,並執行符合的模式所對應的動作。

——阿爾佛雷德·艾侯,The A-Z of Programming Languages: AWK

AWK程序是由一系列模式--動作對組成的,寫做

pattern { action }

其中pattern表示AWK在數據中查找的內容,而action是在找到匹配內容時所執行的一系列命令。輸入行被分成了一些記錄:記錄默認由換行符分割,因此輸入會按照行進行分割。程序使用給定的條件一個個的測試每條記錄,並執行測試通過的條件所對應的actionpatternaction都可以省略不寫。無pattern默認匹配全部的記錄;而無action則是列印原始記錄。簡單的AWK表達式之外,pattern可以是BEGINEND;這兩種條件對應的action分別是讀取所有的記錄之前和之後。同時,如pattern1, pattern2的條件表示符合條件pattern1pattern2的記錄及其之間的部分。

除了一般的,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 )可能類似於某些語言中的變量(例如PHPperl),但在AWK中,它們指代的是當前記錄的域。另外,$0是指整個記錄。事實上,命令printprint $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。由於$4是第4個域的值,$NF,在這種情況下等於$4,則當然是最後一個域的內容。事實上,$是一個具有最高優先級一元運算符。(若一行沒有域,則有NF為0,而$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 (3)函數。這個腳本先建立了一個名為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的程序文件,然後即可運行該程序。

參見[編輯]

外部連結[編輯]