每週一薦:文本處理工具AWK

上週給大家推薦了流編輯器sed,用sed其實已經可以幹很多事情了。批量替換文件,批量查找指定的模式,支持單行和多行模式。但通過”sed單行腳本快速參考“可以看出,sed寫出來的腳本可讀性很差,有些甚至非常難以理解,特別是對於那些對sed的用法不是很熟悉的人來說,看起來簡直像一堆毫無意義的字符亂碼。

腳本寫出來不是給自己看的,也要考慮到後續的維護。有沒有更好的方式來做同樣的事情,甚至比sed能幹更多的事情了。awk登場了,它的語法類似與C,寫起來非常方便並且非常容易理解,提供了許多系統變量、字符串處理函數、數值運算函數、美化輸出函數,甚至可以自己定義函數。能想到的文本分析,AWK貌似都能搞定。下面列出一個關於AWK的快速參考:(悲劇,陪着測試做版本到早晨6點,天都亮了!)

 

命令行語法

調用awk的語法有兩種基本形式:

    awk [-v var=value] [-F re] [--] 'pattern {action}' var=value datafile(s)
    awk [-v var=value] [-F re] -f scriptfile [--] var=value datafile(s)

一個awk命令行是由命令,腳本和輸入文件名組成的。輸入是從命令中指定的文件中讀取的。如果沒有指定文件名爲”-”,那麼將從標準輸入中讀取。選項-F將字段分隔符(FS)設置爲 re。

-v選項在腳本執行前將變量var的值設置爲value。這個在BEGIN過程運行前執行。

根據POSXI參數分析約定,選項”–”標記命令行選項的結束。例如,利用這個選項你可以指定以”-”開頭的datafile,否則這將和命令行選項混淆。

你可以在命令行指定一個由單引號包圍的由pattern和action組成的腳本。換句話說,你也可以將腳本寫入一個單獨的文件並在命令行中用-f選項指定文件名scriptfile。

通過在腳本後命令行上指定參數,可以將它們傳遞到awk中,這包括設置系統變量,例如FO,OFS和RS。value可以是一個文字,一個shell變量($var)或一個命令的結果(`cmd`);如果其中包含空格或製表符則必須用引號包圍起來。可以指定任意多個變量。

直到讀取第一個輸入行。命令行參數才能使用,因此在BEGIN過程中不能訪問。參數按照它們出現在命令行中的順序來求值,直到遇到一個文件名。出現在這個文件名後之後的參數在下一個文件名被識別時變爲可用。

用shell實現調用awk

在系統提示符下輸入腳本只能練習簡單的單行腳本。任何可以作一個命令來調用並重用的腳本都可以放到shell腳本中。利用shell程序來調用awk可以使別人更容易使用這些腳本。

可以將調用awk的命令放在一個文件中,給它取要給名字以說明腳本的功能。將文件做成可以執行的(利用chmod命令)並將它放入到一個目錄中,這個目錄包含全局名。可以在命令行輸入這個shell腳本的名字來執行awk腳本。這是爲了更易於使用和重用。

在現代的unix系統中,包括linux,你可以用#!語法來創建自包含awk腳本:

	#! /usr/bin/awk -f
	script

awk參數和輸入文件名可以在調用shell腳本的命令行上指定。注意,使用的路徑名取決於系統。

awk語言概要

這部分概括了awk如何處理輸入記錄和描述組成awk程序的各種語法要素。

記錄和字段

每個輸入行都被分隔爲字段。默認情況下,字段定界符是一個或多個空格和/或製表符。可以使用-F命令行選項來改變字段分隔符。同時也設置了FS的值。下面命令行將字段分隔符改爲一個冒汗:

	awk -F: -f awkscr /etc/passwd

也可以將定界符複製給系統變量FS。這通常是在BEGIN過程中完成,但也可以作爲命令行的參數來傳遞。

	awk -f awkscr FS=: /etc/passwd

每個輸入行都是幾個字段組成的記錄。每個字段可以根據它在這個記錄中的位置來引用。”$1″表示第一個字段中的值,”$2″表示第二個字段的值等等。”$0″表示整個記錄。下面的操作打印每個輸入行的第一個字段:

	{ print $1 }

默認的記錄分隔符是一個換行符。下面的過程設置了FS和RS,使得awk將直到遇到空行前的任意個行解釋爲一個記錄,而每個行是一個單獨的字段。

	BEGIN { FS="\n"; RS="" }

注意,當RS被設置爲一個空字符時,不過FS的值是什麼,換行符總是用了分隔字段。

腳本的格式

awk腳本包含一系列模式匹配規則和操作:

	pattern {action}

action操作由一個或多個語句組成,用於對那些模式匹配的輸入行執行操作。如果沒有指定模式,這個操作。如果沒有指定模式,這個操作對每個行都執行。下面的例子用print語句打印輸入文件的每個行:

	{ print }

如果指定一個模式,那麼默認的操作由print語句構成,如上所示。

也可以出現函數的定義:

	function name (parameter list) {statements}

這個語法定義了函數name,給出了在函數體中可以訪問的參數列表。在參數列表中指定的變量被看做是函數內部的局部變量,其它所有的變量是全局的並可以在函數以爲對它們進行訪問。當調用自定義函數時,不允許在函數名和左圓括之間有空格個存在。在函數的定義中允許有空格。

行爲終止

awk腳本中的行一個換行符或一個分好符終止。如果允許,可利用分號將多個行放在同一行,但這將降低程序的可讀性。在語句之間允許存在空行。

程序控制語句(do,if,for或while)的範圍包括下一行,在那一行列出了相關語句。如果給出了多個與控制語句相關的語句,那麼必須用大括號括起來。

	if (NF > 1) {
	name = $1
	total += $2
	}

對於使用反斜槓(\)轉義符實現將一行代碼寫在多行上。也可以用下列字符中的任何一個來中斷行。

	, { && ||

在gawk中還是用”?”或”:”來延續一個行。字符串不能跨行(在gawk中除外,在gawk中可以在換行符的前面添加”\”來實現字符串換行)。

註釋

註釋以”#”開始並以換行符結束。它可以單獨出現在一行上或出現在一行的最後。註釋是描述性的說明,用於解釋腳本有操作。註釋不能用反斜槓來延伸到下一行。

模式

一個模式可以是下面所列的任何一個:

	/regular expression/
	relational express
	BEGIN
	END
	pattern,pattern
  1. 正則表達式使用元字符擴展集並且必須用斜槓包圍。
  2. 關係表達式使用關係操作符。
  3. BEGIN模式在第一個輸入行被讀取之前應用,END模式在最後一個輸入行被讀取之後應用。
  4. 使用”!”可以否定匹配,也就是處理與模式不匹配的行。
  5. 可以訪問若干行,和在sed中一樣:pattern, pattern 除了BEGIN和END外,其它模式都可以使用下面的操作符來進行組合:&&(邏輯與), ||(邏輯或)。另外,C的條件操作符?:(pattern ? patttern:pattern)可以用於模式中。
  6. 可以將模式放在圓括號確保正確求值。
  7. BEGIN和END模式必須和操作想聯繫。如果編寫了多個BEGIN和END規則,它們在被應用前被合成一個規則。

正則表達式

正則表達式元字符
特殊字符 用法
c 和不是元字符的任何字符c匹配
\ 轉義它後面的任意元字符,包括它自己
^ 將後面的正則表達定位在字符串的開始處
$ 將前面的正則表達式定位在字符串的末端
. 和任意單個字符匹配,包括換行符
[...] 和用方括號包圍起來的字符類中的任何一個字符匹配、脫字符(^)作爲方括號中的第一個字符表示將匹配所有列在類中的字符以爲的字符,連字符(-)用於表示一個字符範圍。右方括號(])在作爲類中第一個字符時將失去它們原來的含義。但\除外,它可以用轉義],即使沒有用在第一位。
r1 | r2 允許正則表達式r1或r2中的任何一個匹配
(r1)(r2) 用於連接正則表達式
r* 與前面的任意個(包括0個)正則表達式匹配
r+ 與前面正則表達式的一個或多個出現匹配
r? 與前面正則表達式的0個或1個出現匹配
(r) 用於正則表達式的分組
POSIX字符列表工具
符號 功能
[.symbol.] 比較符號。比較符號是一個多字符序列,應將它看做爲一個單元來處理
[=equiv=] 等價類。一個等價類列出了一組字符,這組字符應該被看做是相等的。
[:class:] 字符類。字符類關鍵詞表示不同的字符類。例如字母字符,控制字符等等
[:alnum:] 字母數字字符
[:alpha:] 字母字符
[:blank:] 控制和製表符
[:cntrl:] 控制字符
[:digit:] 數字字符
[:graph:] 可打印的和可見的字符
[:lower:] 小寫字符
[:print:] 可打印的字符
[:punct:] 標點符號字符
[:space:] 空白字符
[:upper:] 大寫字符
[:xdigit:] 十六進制數字

表達式

一個表達式可以由常量、變量、操作符和函數組成。 常量要麼是字符串要麼是數值。變量是一個符號,它引用一個值。可以將它看做是一條信息,返回一個特定數值或字符串的值。

常量

有兩種類型的常量,即字符串常量或數值常量。字符串常量必須用引號括起來,而數值常量不需要。

轉義序列

轉義序列
序列 描述
\a 報警字符
\b 退格符
\f 走紙符
\n 換行符
\r 回車符
\t 水平製表符
\v 垂直製表符
\ddd 將字符表示爲1到3位八進制值
\xhex 將字符表示爲十六進制
\c 任何需要字面表示的字符c

變量

有3種類型的變量:自定義變量,內置變量和字段。按照慣例,內置或系統變量的名字全部由大寫字母組成。

變量的名字不能以數組開頭,而是由字母,數字和下劃線組成,在變量名中字母的大小寫是很重要的。

變量不需要聲明或初始化。一個變量可以包含一個字符串或數值。對於未被初始化的變量將空串(”")作爲它的字符串,將0作爲它的數值。awk會根據操作來決定一個值是作爲字符串還是數值來處理。

變量的賦值形式爲:

	var=expr

它將表達式的值賦給var。下面的表達式將1賦給變量x。

	x=1

使用變量的名字可以訪問它的值:

	{ print x }

以上語句打印變量x的值,本例可以得到1。

參加後面的“系統變量”一節瞭解內置變量的信息。利用n可以訪問字段變量,這裏n是0到NF之間的任意一個數,用於按位置訪問字段。也可以表示爲一個變量,例如NF表示最後一個字段,或表示一個常量,例如$1表示第一個字段。

數組

數組是一個可以用了存儲一組值的變量。下面的語句爲數組的元素賦一個值:

	array[index] = value

在awk中,所有的數組都是關聯數組。使得關聯數組獨特的是它的下標可以是一個字符串也可以是一個數字。

關聯數組在數組的下標和元素之間建立一種“聯繫”。對於數組中的每個元素包含一對值:元素的下標和元素的值。關聯數組的元素不像傳統數組的元素那樣按特定的順序存儲。

可以使用for循環來讀取關聯數組中的所有元素。

	for (item in array)

這裏數組的下標由變量item來指定,數組元素的值可以利用array[item]來測試這個元素的值。也可以使用delete語句從數組中刪除一個元素。

系統變量

系統變量
變量 描述
ARGC 命令行中的參數個數
ARGV 包含命令行參數的數組
CONVFMT 用於數字的字符串串轉換格式(%.6g)
ENVIRON 環境變量的關聯數組
FILENAME 當前文件名
FNR 和NR類似,但和當前文件相關
FS 字段分隔符(一個空格)
NF 當前記錄中的字段個數
NR 當前記錄的個數
OFMT 數字的輸出格式(%.6g)
OFS 輸出字段分隔符(一個空格)
ORS 輸出記錄分隔符(一個換行符)
RLENGTH 和函數match()匹配的字符串的長度
RS 記錄分隔符(一個換行符)
RSTART 和函數match()匹配的字符串的第一個位置
SUBSEP 數組下標的分隔字符(\034)

操作符

操作符
操作符 描述
= += -= *= /= %= ^= **= 賦值操作符
?: C語言的條件表達式
|| 邏輯或
&& 邏輯與
~ !~ 匹配正則表達式與不匹配
< <= > >= != == 關係操作符
(blank) 連接符
+ - 加法,減法
* / % 乘法,除法,取模
+ – ! 正,負,邏輯非
^ ** 求冪
++ – 遞增和遞減,作爲前綴和後綴
$ 字段引用

語句和函數

包含在大括號中的動作,由一個或多個語句和/或表達式組成。語句和函數之間的區別是函數將返回一個值,並且它的參數列表在圓括號中給出(在形式上不一定嚴格按照語法規定:printf被看做是一個語句,而它的參數列表可以包含在圓括號中;getline是一個函數,但它沒有使用括號 )。

awk有許多預定義的算術函數和字符串函數。函數經常按下面的方式調用:

	return = function(arg1, arg2)

這裏的return是一個變量,用於保存函數的返回值(實際上,一個函數的返回值可以用在表達式的任何位置,而不僅僅只出現在賦值語句的右邊)。函數的自變量是用逗號分隔的一個列表。左圓括號跟在函數名後面(對於內置函數,在函數名和括號之間允許有一個空格)。

awk的命令彙總

awk命令彙總
命令 說明 例子
atan2() atan2(y,x)返回y/x的反正切,單位是弧度。  
break 從while, for, do循環中退出  
close() close(filename-exppr)close(command-expr) 在大多數awk的是是實現中,你只能同時打開一定數量的文件和/或管道。因此,awk提供了一個close()函數,利用這個函數可以關閉一個文件或應該管道。它將打開文件或管道的同一表達式作爲參數。這個表達式的每個字符必須和打開文件或管道所用的表達式相同——即使空格也是有意義的。  
continue 開始while, for, do循環的下一個迭代  
cos() cos(x)返回x的餘弦,x單位爲弧度  
delete delete array[element]刪除數組的元素  
do do body while(expr) 循環語句。執行在body中的語句並計算expr的值,當expr的值爲真時,重複執行body。  
exit exit [expr]從腳本中退出,不再讀取新行。如果有END規則,則執行。選項expr是awk的返回值。  
exp() exp(x)返回e的x次冪(e^x)  
for for(init-expr; test-expr; incr-expr) statementC語言風格的循環結構。init-expr是計算器變量的初始值。test-expr是一個關係表達式並在每次執行statement之前計算它的值。當test-expr爲假的時,退出循環。incr-expr用來在每次循環中遞增計數器變量。 

for(item in array) statement

用於讀取關聯數組的特殊循環。對於數組中的每個元素,statement都被執行;可利用array[item]的形式來訪問元素。

 
getline 讀取下一輸入行getline [var] [ 

第一種形式從file中讀取輸入,第二種形式讀取command的輸出結果。兩種形式每次都只讀取一行,並且每次執行該語句得到下一個輸入行。輸入行被賦給0並分解爲字段,且設置NF,NR,FNR。如果指定了var,那麼結構將賦給var而0不變。因此當結果被賦給一個變量時,當時行沒有變化。實際上getline是一個函數,當它成功讀取一個記錄時返回值1,當遇到最後一行時返回0,當由於某些原因讀取失敗返回時返回-1。

 
gsub() gsub(r, s, t)全局替換字符串s中與字符傳t中的正則表達式r匹配的所有字符串。返回替換的次數,如果t沒有給出,默認值爲$0。  
if if (expr) statement1 [else statement2] 條件語句。計算expr的值,如果爲真,執行statement1;若果給出了else子句,當expr爲假時執行statement2。  
index() index(str, substr)返回字符串中的子串的位置(起始位置爲1)  
int() int(x)通過將x中的小數點後面的數字截斷得到x的整數值。  
length() length(str)返回字符串的長度,如果沒有參數則返回$0的長度。  
log() log(x)返回x的自然對數(以e爲底)  
match match(s, r)模式匹配函數,由正則表達式r給出模式,返回在字符串s中與r匹配的開始位置,如果沒有發現匹配則返回0。將RSTART和ELENGTH的值分別設置爲匹配的開始位置和匹配的長度。  
next 讀取下一個輸入行並從第一個規則開始執行腳本  
print print [output-expr] [dest-expr]求output-expr的值並將它直接輸出到標準輸出,後面跟着ORS的值。每個output-expr都被ORS的值分隔開。dest-expr是一個可選表達式,直接將輸出送到一個文件或管道。”>file”直接將輸出送到一個文件,並覆蓋它的以前內容。”>>file”將輸出追加到一個文件中,保留它以前的內容。在這種情況下,如果文件不存在都將創建這個文件。”|command”直接將輸出作爲一個系統命令的輸入。  
printf printf (format-expr[, expr-list]) [dest-expr]從C語言中借用一個可選的輸出語句。它可以產生格式化輸出。它也可以用於輸出沒有自動換行的數據。format-expr是一個格式說明字符串和常量字符串(參加下一節關於格式說明符的列表)。expr-list是一個和格式說明符對應的參數列表。參加print語句關於dest-expr的描述。  
rand() rand()生成0到1之間的一個隨機數。每次執行腳本時這個函數返回相同一系列數據,除非使用srand()函數生成隨機數發生器的種子數。  
return n return [expr]使用用戶定義的終端退出函數,返回表達式的值  
sin() sin(x)返回x的正弦,x單位爲弧度  
split() split(str, array, sep)這個函數利用字段分隔符將字符串分解到數據元素中。如果沒有指定字段分隔符,則使用FS的值。數組的分隔和字段的分隔是相同的。  
sprintf() sprintf(format-expr[, expr-list])該函數返回根據printf格式說明指定的格式化的字符串。它格式化數據但不輸出數據。format-expr是一個格式說明字符串和常量字符串。expr-list是一個和格式說明符對應的參數列表。  
sqrt() sqrt(x)返回x的平方根  
srand() srand(expr)使用expr爲隨機數發生器設置一個種子數。默認值爲當天的時間。返回值爲舊的種子數。  
sub sub(r, s, t)替換字符串s中與字符串t中的正則表達式r匹配的第一個字符串。如果成功則返回1,否則返回0.如果沒有給出t,默認值爲$0。  
substr() substr(str, beg, len)返回字符串str中開始位置爲beg的子串,後面的字符串最大長爲len。如果沒有給出長度,將得到剩餘的字符串。  
system() system(command)該函數執行給出的command並返回它的狀態。執行命令的狀態通常表示成功或失敗。0表示命令執行成功。非0表示某些類型的錯誤,不管是正的或負的,所執行的命令的有關文檔將提供詳細的介紹。在awk腳本中這個命令的輸出結果是不可用的。使用”command | getline”可以將命令的輸出讀取到腳本中。  
tolower() tolower(str)將字符串str中的所有大寫字母轉換爲小寫字母並返回新的字符串。  
toupper toupper(str)將字符串str中的所有小寫字母轉換爲大寫字母並返回新的字符串。  
while while(expr) statement循環結構。當expr爲真時,執行statement。  

應用在printf和sprintf中的格式表達式

格式表達式可以在%之後有3個可選的修飾符,格式說明符:

	%-width.precision format-specifier

輸出字段width是一個數值型的值。當你指定字段的寬度時,該字段的內容默認爲右對齊。必須指定”-”來實現左對齊。因此,”%-20″輸出的是一個字段寬度爲20個字符的左對齊的字符串。如果這個字符串不足20個字符,則用空格來填滿字段。

precision修飾符用於調整十進制浮點型的值,控制小數點左邊出現的數字的個數。對於字符串格式,它控制從這個字符串中打印的字符的個數。

你可以根據printf或sprintf中的參數列表爲width和precision動態賦值。要實現這一功能必須註上星號,而不是指定字面值。

	printf("%*.*g\n", 5, 3, myvar)

在這個例子中,寬度爲5,精度爲3,將要打印的值來自於myvar。

注意,數值輸出的默認精度是”%.6g”。這個默認值可以通過設置系統變量OFMT來改變。這將影響用print語句輸出值的精度。

printf中使用的格式說明符
字符 描述
C ASCII字符
d 十進制整數
i 十進制整數,已經添加到POSIX中
e 浮點型格式([-]d.precisione[+-]dd)
E 浮點型格式([-]d.precisionE[+-]dd)
f 浮點型格式([-]ddd.precision)
g e或f的輪迴,無論哪一個最短,將末尾的0刪除
G E或f的輪迴,無論哪一個最短,將末尾的0刪除
o 無符號的八進制值
s 字符串
x 無符號的十六進制,用a-f表示10-15
X 無符號的十六進制,用A-F表示10-15
% 字面%

printf和sprintf的舍入方式通常取決於系統的C sprintf子例程。在許多機器上,sprintf舍入是“無偏差的”,這意味着它不總是對“.5”進行進位,因此1.5舍入得2。而4.5的舍入卻爲4。因此如果你利用一個格式來進行舍入計算,你應該知道你的系統是如何處理的。

參考資料

  1. 《sed and awk》

2012/04/27 05:56 於上海

發佈了88 篇原創文章 · 獲贊 31 · 訪問量 21萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章