Sed&awk筆記之awk篇:快速瞭解Awk(一)

tuantuan發表於2017-01-10
Sed&awk筆記之Sed篇完成之後,我又偷懶了一段時間,昨天狠狠地給自己抽根鞭子,一定要把這半個坑填上。相信看過Sed篇的同學都清楚,這一系列文章雖名日筆記,事實上有很多我自己個人的理解,加上遣詞造句都是我自己的內容,所以轉載一定要標明出處,詳見文章後方。

Awk是什麼

Awk、sed與grep,俗稱Linux下的三劍客,它們之間有很多相似點,但是同樣也各有各的特色,相似的地方是它們都可以匹配文字,其中sed和awk還可以用於文字編輯,而grep則不具備這個功用。sed是一種非互動式且面向字元流的編輯器(a “non-interactive” stream-oriented editor),而awk則是一門模式匹配的程式語言,因為它的主要功能是用於匹配文字並處理,同時它有一些程式語言才有的語法,例如函式、分支迴圈語句、變數等等,當然比起我們常見的程式語言,Awk相對比較簡單。 使用Awk,我們可以做以下事情:

  • 將文字檔案視為由欄位和記錄組成的文字資料庫;
  • 在操作文字資料庫的過程中能夠使用變數;
  • 能夠使用數學運算和字串操作
  • 能夠使用常見的程式設計結構,例如條件分支與迴圈;
  • 能夠格式化輸出;
  • 能夠自定義函式;
  • 能夠在awk指令碼中執行UNIX命令;
  • 能夠處理UNIX命令的輸出結果;

裝備以上功能,awk能夠做得事情非常多。但千里之行,始於足下,我們首先從最基本的命令列語法開始,一步一步得走入awk的程式設計世界。

命令列語法

同sed一樣,awk的命令列語法也有兩種形式:


awk [-F ERE] [-v assignment] ... program [argument ...]
awk [-F ERE] -f progfile ...  [-v assignment] ...[argument ...]

這裡的program類似sed中的script,因為我們一直強調awk是一門程式語言,所以將awk的指令碼視為一段程式碼。而awk的指令碼同樣可以寫到一個檔案中,並通過-f引數指定,這一點和sed是一樣的。program一般多個pattern和action序列組成,當讀入的記錄匹配pattern時,才會執行相應的action命令。這裡有一點要注意,在第一種形式中,除去命令列選項外,program引數一定要位於第一個位置。 Awk的輸入被解析成多個記錄(Record),預設情況下,記錄的分隔符是
,因此可以認為一行就是一個記錄,記錄的分隔符可以通過內建變數RS更改。當記錄匹配某個pattern時,才會執行後續的action命令。 而每個記錄由進一步地被分隔成多個欄位(Field),預設情況下欄位的分隔符是空白符,例如空格、製表符等等,也可以通過-F ERE選項或者內建變數FS更改。在awk中,可以通過$1,$2…來訪問對應位置的欄位,同時$0存放整個記錄,這一點有點類似shell下的命令列位置引數。關於這些內容,我們會在下面詳細介紹,這裡你只要知道有這些東西就好。 標準的awk命令列引數主要由以下三個:

  • -F ERE:定義欄位分隔符,該選項的值可以是擴充套件的正規表示式(ERE);
  • -f progfile:指定awk指令碼,可以同時指定多個指令碼,它們會按照在命令列中出現的順序連線在一起;
  • -v assignment:定義awk變數,形式同awk中的變數賦值,即name=value,賦值發生在awk處理文字之前;

為了便於理解,這裡舉幾個簡單的例子。通過-F引數設定冒號:為分隔符,並列印各個欄位:


[kodango@devops ~]$ echo "1:2:3" | awk -F: `{print $1 " and " $2 " and " $3}`
1 and 2 and 3

在awk的指令碼中訪問通過-v選項設定的變數:


[kodango@devops ~]$ echo | awk -v a=1 `BEGIN {print a}`
1

從上面可以看到,通過-v選項設定的變數在BEGIN的位置就可以訪問了。BEGIN是一個特殊的pattern,它在awk處理輸入之前就會執行,可以認為是一個初始化語句,與此對應的還有END。 好像還沒介紹如何指定處理的檔案,是不是最後的argument就是指定的檔案?在看我這本書之前,我也是這樣認為的,但是實際上arguemnt有兩種形式,它們分別是輸入檔案(file)和變數賦值(assignment)。 awk可以同時指定多個輸入檔案,如果輸入檔案的檔名為`-`,表示從標準輸入讀取內容。 變數賦值類似-v選項,它的形式為name=value。awk中的變數名同一般的程式語言無太多區別,但是不能同awk的保留關鍵字重名,可以檢視awk的man手冊查詢哪些是保留關鍵字。而變數值只有兩種形式:字串和數值。變數賦值必須位於指令碼引數的後面,與檔名引數無先後順序的要求,但是位於不同位置的賦值它的執行時機是不同的。 我們用實際的例子來解釋這個區別,假設有兩個檔案:a和b,它們的內容分別如下所示:


[kodango@devops awk_temp]$ cat a
file a
[kodango@devops awk_temp]$ cat b
file b

為了說明賦值操作發生的時機,我們在BEGIN,正常處理,END三個地方都列印變數的值。 第一種情況: 變數賦值位於所有檔名引數之前


[kodango@devops awk_temp]$ awk `BEGIN {print "BEGIN: " var} {print "PROCESS: " var} 
END {print "END: " var }` var=1 a
BEGIN: 
PROCESS: 1
END: 1

結果:賦值操作發生在正常處理之前,BEGIN動作之後。 第二種情況:變數賦值位於所有檔名之後:


[kodango@devops awk_temp]$ awk `BEGIN {print "BEGIN: " var} {print "PROCESS: " var} 
END {print "END: " var }` a var=1  
BEGIN: 
PROCESS: 
END: 1

結果:賦值操作發生在正常處理之後,END動作之前。 第三種情況:變數賦值位於檔名之間:


[kodango@devops awk_temp]$ awk `BEGIN {print "BEGIN: " var} {print "PROCESS: " var} 
END {print "END: " var }` a var=1 b
BEGIN: 
PROCESS: 
PROCESS: 1
END: 1

結果:賦值操作發生在處理前面的檔案之後,並且位於處理後面的檔案之前; 總結如下:

  1. 如果變數賦值在第一個檔案引數之前,在BEGIN動作之後執行,影響到正常處理和END動作;
  2. 如果變數賦值在最後一個檔案引數之後,在END動作之前執行,僅影響END動作;
  3. 如果檔案引數不存在,情況同1所述;
  4. 如果變數賦值位於多個檔案引數之間,在變數賦值前面的檔案被處理後執行,影響到後續檔案的處理和END動作;

所以變數賦值一定要考慮清楚用途,否則比較容易出錯,不過一般情況下也不會用到變數賦值。 自然地大家會將變數賦值與-v assignment選項進行比較,賦值的形式是一致的,但是-v選項的執行時機比變數賦值要早:


[kodango@devops awk_temp]$ echo 1 | awk -v var=a `BEGIN {print "BEGIN: " var}`
BEGIN: a

可見,-v選項的賦值操作在BEGIN動作之前就執行了。 變數賦值一定要小心不要與保留關鍵字重名,否則會報錯:


[kodango@devops awk_temp]$ echo 1 | awk -v BEGIN=1 `BEGIN {print "BEGIN: " BEGIN}`
awk: fatal: cannot use gawk builtin `BEGIN` as variable name

本篇文章主要介紹了awk的命令列語法,下一篇的主題是awk語言的基礎元素與概念。