AWK是一門解釋型的編程語言,它的名字來源于它的三位作者的姓氏:Alfred Aho,Peter Weinberger和Brian Kernighan。AWK能夠應用于廣泛的計算和數據處理任務。所有的GNU/linux發行版都自帶GAWK,即GNU AWK,是AWK的擴展并且與AWK完全兼容。
和上面講到的sed命令類似,AWK逐行讀取輸入流中的內容,并對讀取的行執行所有命令,如此循環,直到輸入流結束。
本文基于GAWK進行介紹,因為GAWK比原生AWK使用更普遍些。
AWK命令格式如下:
awk [options] 'PRogram' input-file1 input-file2 ...
或者
awk [options] -f program-file input-file1 input-file2 ...
第一種格式中,awk從file中獲取輸入流,然后執行單引號內的程序。第二種格式則是從文件program-file中獲取將要執行的程序。
上述AWK命令的program部分的結構可分為三塊:BEGIN、BODY和END。
BEGIN:在AWK命令的一開始執行的動作,它只執行一次,可以把變量初始化放在這里。注意,BEGIN部分是可選的,并且一個AWK命令中可以有多個BEGIN塊。注意,如果有-v選項的賦值操作,則-v的操作在BEGIN之前。
BEGIN塊的寫法為:BEGIN{…}
BODY:程序主體,對輸入流的每一行執行動作。如果存在BEGIN或END,則這部分是可選的。一個AWK命令中可以有多個BODY塊。
BODY塊的寫法為:{…}
END:在AWK命令的最后執行的動作,它只執行一次。注意,END部分是可選的,并且一個AWK命令中可以有多個END塊。注意,使用END { }塊時,awk的file參數不能省略。
END塊的寫法為:END{…}
例如下面的命令:[root@Ubuntu]awk_test:$ awk 'BEGIN{print "Output start!"}; {print}; END{print "Output done!"}' awk_test.txt Output start!USER PID %MEM VSZ rss STAT START TIME COMMANDroot 1 0.1 3652 1916 Ss Jan07 0:03 /sbin/initroot 2 0.0 0 0 S Jan07 0:00 [kthreadd]root 3 0.0 0 0 S Jan07 0:02 [ksoftirqd/0]root 26 0.0 0 0 S Jan07 0:40 [kswapd0]user 495 0.1 3588 1092 Ss Jan07 0:00 /sbin/udevd --daemonuser 860 0.0 3584 908 S Jan07 0:01 /sbin/udevd --daemonuser 1137 0.0 4520 776 S Jan07 0:00 smbd -Fuser 1550 0.1 4521 1816 Ss Jan07 0:15 nmbd -DOutput done!可以看到這條命令在命令的開始打印一行輸出“Output start!”,然后對文件中的每一行內容執行print操作,最后又打印出一行輸出“Output done!”。
從句法結構上來講,program由一條條規則組成,每條規則由模式和動作組成,即模式匹配后執行相應的動作。動作放在花括號內以與模式區分。所以,program一般的格式是這樣的:
pattern { action }
pattern { action }
…
那么,下面這條命令(打印長度大于80字符的行):
awk 'length($0) > 80' data可以看到這條awk命令只有pattern而沒有action部分。如果沒有action部分,則執行默認動作:打印整個record。
也可以把program寫到文件里(不用加單引號),通過AWK的第二種格式來執行。[root@ubuntu]awk_test:$ cat progfile BEGIN{print "Output start!"};{print};END{print "Output done!"};[root@ubuntu]awk_test:$ awk –f progfile awk_test.txt為了方便后期維護,建議將AWK的程序文件以.awk作為后綴名。
另外,可以利用Shell的#!機制,將progfile內容改為:#!/usr/bin/awk –fBEGIN{print "Output start!"};{print};END{print "Output done!"};這樣的話,執行./progfile awk_test.txt即可。(通過type awk可以知道系統中awk命令的位置。)
注:使用#!機制的話,執行的shell命令./progfile實際上是執行:"#!后面的命令" +"./progfile腳本" + "./progfile腳本的參數"。另外,這種寫法,awk后面最多只能跟一個參數;并且ARGV[0]的值在不同系統上可能表現不同,比如可能被解釋為awk或/usr/bin/awk或./progfile。
比較特別的,如果要在program內使用或打印單引號,可以用其ASCII碼'/47'表示。或者把程序寫在文件中,這樣就不用擔心單引號和program外圍的單引號混淆的問題。注意,在單引號中,反斜杠后接一個字符,會被解釋成這個字符的字面意思,即和不加反斜杠是一樣的含義。
你也可以通過下面兩種方法來打印單引號:
awk 'BEGIN { print "Here is a single quote <'"'"'>" }'或
awk 'BEGIN { print "Here is a single quote<'/''>" }'AWK選項
-f program-file 執行文件中的程序,前面已講過。Program-file可以有多個,通常用來將通用的代碼或函數做成庫,以實現代碼復用。
環境變量AWKPATH用來指定-f的搜索路徑,如果不指定AWKPATH,則默認搜索“.:/usr/local/share/awk”,可以通過修改AWKPATH或ENVIRON["AWKPATH"]來修改搜索路徑,每個路徑之間用冒號隔開(.或::都可以表示當前路徑)。如果-f選項后面跟的是包含“/”的文件名,就不會去額外搜索路徑了。
-v var=value 變量賦值,在BEGIN之前進行。例如,定義變量name,并賦值為“jason”:[root@ubuntu]awk_test:$ awk -v name=jason 'BEGIN{printf("name=%s/n", name)}'name=jason注意變量的引用不需要加“$”,實際上AWK的語法很多跟ANSI C的語法類似。“$”在AWK中是用來引用field的,后面會講到。
-F fs 使用fs作為分隔符(默認是空格)例如,打印/etc/passwd文件中的用戶名一列:[root@ubuntu]awk_test:$ cat /etc/passwd | awk -F ':' '{print $1}'rootdaemonbinsyssyncgames --compat 或 --traditional 使用原生awk,不會識別gawk的擴展。例如,原生awk允許在while/for/do循環外面使用continue和break語句,這會被認為和next語句意思相同。Gawk添加--traditional則可以使用這一特性。 --dump-variables[=file] 輸出排好序的AWK內置的全局變量到文件(若不指定文件,則默認為awkvars.out)這個選項可以查看當前可用的內置全局變量。在編程的時候要注意,不要定義和這些內置變量重名的變量。
--non-decimal-data 識別輸入流中的十六進制和八進制例如,將下面文件中的數字相加求和:[root@ubuntu]awk_test:$ cat number.txt 0x120x3201210[root@ubuntu]awk_test:$ awk '{sum+=($1)}; END{print sum}' number.txt 22[root@ubuntu]awk_test:$ awk --non-decimal-data '{sum+=($1)}; END{print sum}' number.txt 88可以看到,如果不加--non-decimal-data選項,就只識別出十進制的12和10。
不過man gawk中說“Use this option with great caution!”,一方面這個選項可能破壞舊的程序,另一方面這個選項可能在以后被摒棄。
--profile[=prof_file] 生成awk命令的profile文件這個選項以優雅的格式將awk命令保存到文件,如果不指定文件名,則默認為awkprof.out。[root@ubuntu]awk_test:$ awk --non-decimal-data --profile '{sum+=($1)}; END{print sum}' number.txt[root@ubuntu]awk_test:$ cat awkprof.out # gawk profile, created Sun Jan 8 23:20:48 2017 # Rule(s) { sum += $1 } # END block(s) END { print sum }如果使用pgawk執行命令,則還會顯示每條語句以及每個函數的調用次數。
--re-interval 在正則表達式中支持間隔表達式傳統的awk不支持間隔表達式,必須加上--re-interval選項或者--posix選項才能使用。
-e program-text 或 --source program-text program-text為awk的program源碼,這個選項允許將文件中的源碼和命令行中的源碼混合使用。在需要引用自定義的庫函數時就可以使用該選項,例如:awk -f func_test.awk --source 'BEGIN{printadd_INT(1,2)}' awk_test.txt
這個例子中,func_test.awk里面定義了函數add_INT(),這里將-f指定的文件程序和--source指定的命令行程序混合在了一起。
-E file 或 --exec file 意義和-f選項相同,不過有兩點區別:命令行中的其他選項都直接傳給awk,而awk先處理其他選項和參數,最后才處理--exec選項;另外,不允許“var=value”形式的變量賦值。這個選項應該在#!開頭的腳本里使用,例如:
#! /usr/local/bin/gawk -E awk program here …這個選項可以防止向腳本里傳遞參數,因為所有的參數都先被awk識別并處理了。
--include source-file--load ext這兩個選項都是針對引用函數庫的,也可以在文件中使用@include和@load來引用庫文件。不過在我所用系統的gawk不支持這兩個參數以及相應的AWKLIBPATH環境變量,我就不介紹了。我們就使用-f來引用庫文件吧,只是-f的文件里面可以是任何程序內容,并不是只針對庫文件而設計的。
-- 標記選項的結束這告訴awk選項部分已經結束,可以用來傳遞以“-”開頭的參數,而不被誤認為是選項。
AWK的變量、Records和Fields
AWK的變量是動態生成的,在第一次被使用的時候開始存在。變量的值可以是下列數據類型:浮點型、字符串類型和一維數組。甚至一個變量既可以是浮點型也可以是字符串類型,這取決于代碼中如何使用它。
AWK會將“var=value”形式的參數認為是變量賦值,例如下面這個命令,awk將“var=2”和“var=1”認為是給var賦值,而不是一個文件名,這個過程在awk順序處理參數列表時進行的。
awk 'var == 1 {print 1} var == 2 { print 2}' var=2 awk_test2.txt var=1 awk_test1.txtRecords
一個record就是awk認為的一行輸入,對一個輸入流,默認以換行符分隔。不過可以通過內置變量RS來修改。例如,把RS賦值為Jan07:
[root@ubuntu]awk_test:$ awk -v RS=Jan07 '{print}' awk_test.txt USER PID %MEM VSZ RSS STAT START TIME COMMANDroot 1 0.1 3652 1916 Ss 0:03 /sbin/initroot 2 0.0 0 0 S 0:00 [kthreadd]root 3 0.0 0 0 S 0:02 [ksoftirqd/0]root 26 0.0 0 0 S 0:40 [kswapd0]user 495 0.1 3588 1092 Ss 0:00 /sbin/udevd --daemonuser 860 0.0 3584 908 S 0:01 /sbin/udevd --daemonuser 1137 0.0 4520 776 S 0:00 smbd -Fuser 1550 0.1 4521 1816 Ss 0:15 nmbd -D看效果就知道是什么意思了。注意“Jan07”本身沒有打印出來。
如果RS被賦值為單個字符,則這個字符就是records的分隔符;如果被賦值為多個字符,那RS實際上是一個正則表達式。
如果RS被賦值為空,則以空行作為record的分隔符。fields
AWK會把一個record再分隔成一個個的field依次進行處理,以變量FS的值作為分隔符。
如果FS被賦值為單個字符,則這個字符就是fields的分隔符;如果被賦值為多個字符,那就是一個正則表達式;如果FS是空,則record中的每個字符都是分隔符。
注意,如果FS是空格,則多個空格、tab和換行,都會被認為是分隔符。
一個record中的各個field可以通過各自的位置來引用,依次為$1,$2,$3,...。而$0表示整個record。
如果給FIELDWIDTHS變量賦值為一個數值列表(以空格隔開),如下面的例子,record就會按照字符串長度來分隔fields而不是按照FS的值。這時FS變量會被忽略。[root@ubuntu]awk_test:$ awk 'BEGIN{FIELDWIDTHS="2 4 4 4"}{print $1"||"$2"||"$3"||"$4}' awk_test.txt US||ER || || PIDro||ot || || 1ro||ot || || 2ro||ot || || 3ro||ot || || 26us||er || || 495us||er || || 860us||er || ||1137us||er || ||1550如果給FS重新賦值,就會切回到按照FS分隔fields。
NF變量用于獲取當前record共分了多少了fields。你可以給NF、$0以及某個field賦值,那么相應record會更新并重新劃分fields。例如:[root@ubuntu]awk_test:$ awk '{NF-=1; if (FNR!=1) $1="json"; print}' awk_test.txt USER PID %MEM VSZ RSS STAT START TIMEjson 1 0.1 3652 1916 Ss Jan07 0:03json 2 0.0 0 0 S Jan07 0:00json 3 0.0 0 0 S Jan07 0:02json 26 0.0 0 0 S Jan07 0:40json 495 0.1 3588 1092 Ss Jan07 0:00 /sbin/udevdjson 860 0.0 3584 908 S Jan07 0:01 /sbin/udevdjson 1137 0.0 4520 776 S Jan07 0:00 smbdjson 1550 0.1 4521 1816 Ss Jan07 0:15 nmbd這個例子中,把NF減1,則原來的每個record的最后一個field就沒有打印出來。FNR表示當前已經輸入了幾個record,例子中將文件除了第一行之外的第一個field的值都改成了“jason”。內置變量
AWK有一些內置的全局變量可以直接使用,會使編程更方便。
內置變量 | 含義 |
ARGC | Awk命令行的參數個數(不包括選項和選項的值以及program部分),awk自身是第0個參數。 |
ARGV | Awk的參數列表,共ARGC個元素。 |
ARGIND | 當前正在處理的文件處于ARGV數組中的index。 |
BINMODE | 是否使用binmode打開文件。1("r"),2("w"),3("rw")分別表示輸入文件、輸出文件、所有文件需要使用binmode打開。 |
CONVFMT | 數字的格式化類型,默認是"%.6g"。 |
ENVIRON | 保存當前所有環境變量的數組。這個數組是以環境變量名作為下標的,例如ENVIRON["HOME"]的值是"/root"。 |
ERRNO | 當getline或close失敗,ERRNO會保存字符串形式的錯誤描述。 |
FIELDWIDTHS | 一個以空格分隔的數值列表,設置這個值后,FS失效,record改以字符長度來劃分fields。 |
FILENAME | Awk當前處理的文件的名字。注意,在BEGIN{ }中由于還沒開始處理文件,FILENAME是空(除非被getline設置)。 |
FNR | 表示正在處理的文件目前已經輸入了幾個record。 |
FS | Field分隔符,默認是空格。 |
IGNORECASE | 是否忽略大小寫,默認是0。用于控制正則表達式和字符串操作,例如會影響records和fields的分隔符。注意,數組下標不受影響。 |
LINT | 如果為true,則對可能不兼容的awk命令提示warning,如果為fatal,則提示為錯誤。默認是0:不提示。 |
NF | 當前record中的fields的數目。 |
NR | 已經處理的record的數目(包括當前record)。 |
OFMT | 輸出中數字的格式,默認是“%.6g”。 |
OFS | 輸出中fields的分隔符,默認是空格。例如下面的命令,就會以” : ”來打印fields(注意$1 $2 $3之間要加逗號): awk -v OFS=" : " '{print $1,$2,$3}' awk_test.txt |
ORS | 輸出中records的分隔符,默認是新行。 |
PROCINFO | 一個包含正在執行的awk命令自身信息的數組,以元素名作為下標,例如PROCINFO[“egid”],PROCINFO[“pid”]等,下面的命令可以列舉所有的元素: awk '{for (var in PROCINFO) print var;}' awk_test.txt |
RS | records分隔符,默認是新行。 |
RT | record的結束符,通常被賦值為RS。 |
RSTART | match()匹配成功的子串的第一個字符在原始字符串中的index(從1開始)。0表示匹配失敗。 |
RLENGTH | match()匹配成功的子串的長度。-1表示匹配失敗(可以匹配空串,此時長度為0)。 |
SUBSEP | 數組下標分隔符,默認為/034即0x1C的ASCII符號。在訪問多維數組時有用。 |
TEXTDOMAIN | 文本域,本地化的時候用到。 |
對于FS,強調一個‘FS =" "’和‘FS = "[ /t/n]+"’的不同,這兩種FS都是將多個空格、tab或newline作為分隔符。但是前者會先將record中的前導空格和尾部空格剝掉,再決定如何分割fields。例如下面兩條命令的結果就不同:
[root@ubuntu]awk_test:$ echo ' a b c d ' | awk '{ print $2 }'b[root@ubuntu]awk_test:$ echo ' a b c d ' | awk 'BEGIN { FS = "[ /t/n]+" }{ print $2 }'A也可以據此特征來刪除前導空格:[root@ubuntu]awk_test:$ echo ' a b c d' | awk '{ print; $2 = $2; print }' a b c da b c d這條命令通過$2=$2,讓awk重新劃分fields,由于FS模式是空格,這時就會先把前導空格和尾部空格剝掉。
數組
AWK中的數組是關聯數組(鍵值對的形式),數組下標放在方括號“[ ]”內,數組下標可以是數值或字符串。可以用(expr, expr ...)來模擬多維數組的下標。例如:i = "A"; j = "B"; k = "C"x[i, j, k] = "hello, world/n"上面定義了數組x下標為"A/034B/034C"的值為"hello,world/n"。
操作符“in”可以用來測試下標是否存在,例如:if (val in array)print array[val]如果是多維的下標,則寫成 if ((i, j) in array) print array[i, j]。
也可以用“in”來遍歷數組:
下面兩個例子,前者定義了一個一維數組,數組下標是[“a,b”]的元素。后者用來定義多維數組,例子中定義了三維數組中下標為[”a”][“,”][”b”]的元素。[root@ubuntu]awk_test:$ awk 'BEGIN{array["a"",""b"]=1;for(i in array) print i}'a,b[root@ubuntu]awk_test:$ awk 'BEGIN{array["a",",","b"]=1;for(i in array) print i}'a/034,/034b記住,數組的小標總是字符串,如果以數值為下標,也會轉換成字符串,也就是說,a[17]的下標是"17",并且和a[021]、a[0x11]是相同的下標。
使用delete可以刪除一個數組元素,例如delete array[1],也可以delete array來刪除整個數組。
變量類型轉換
變量和fields可以是字符串或浮點數類型,一個變量和field的值是什么類型依賴于它處于的上下文,例如,如果用于數學表達式,就被當為數值類型。
如果需要強制變量或field被當作數值,則寫成var+0。如果需要強制被當作字符串,則連接一個空串,例如var ""。
雖然所有的數值都是浮點型的,但是對于一個整數,轉換成字符串的結果就是整型,例如12會被轉換為“12”而不是“12.00”。
在比較兩個變量var1和var2時,如果兩個變量都是浮點型,則按照數值來比較;如果一個浮點型,另一個是字符串,則按照數值來比較,這時,字符串變量被當做“數值字符串”,例如("12"==12)的值為1;如果兩個變量都是字符串,則按照字符串來比較。
注意,只有在處理用戶輸入時,才會將形如“57”這種看起來像數值的字符串認為是“數值字符串”,例如getline的輸入、FILENAME、ARGV的成員、ENVIRON的成員以及split()產生的數組元素。其他情況下,則只是一個字符串常量。
未初始化的變量,會有兩個默認的初始值:數值0以及空字符串""。八進制和十六進制
gawk識別八進制和十六進制,例如011、0x16。
字符串常量
字符串常量是指雙引號內的字符序列。在字符串中,可以包含轉義字符,如下:
轉義字符 | 含義 |
// | 字面含義的反斜杠 |
/a | “alert”字符,通常是ASCII中的BEL |
/b | backspace,回車 |
/f | form-feed,換頁符 |
/n | newline,換行符 |
/r | carriage return,回車 |
/t | horizontal tab |
/v | vertical tab |
/xhex | 十六進制數表示的ASCII碼字符。例如/x2A表示ASCII中的“*”號 |
/ddd | 一個、兩個或三個數字組成的八進制數值表示的ASCII碼字符。例如/052表示ASCII中的“*”號,注意/52同/052相同 |
/c | 字面含義的字符c,例如需要使用原意的*號,則需要寫成/*。 |
AWK是一個line-oriented的語言,對每一行(record),先進行模式匹配,再執行動作。動作的語句放在花括號{ }里面。如果模式為空,則對所有record執行動作,例如 { print } ,就是無條件的打印整個record。
用“#”對一行程序進行注釋,直到換行則注釋內容結束。
正常情況下,一個完整的語句以換行結束,但一個語句也可以寫成多行,如果一個語句在“,”、“{”、“?”、“:”、“&&”或“||”處換行,則認為該語句尚未完成;一個語句在一行中以do或else結尾,也會認為該語句未完成而繼續讀取下一行;其他情況下,可以在行末添加“/”來標記這一行尚未結束(建議不要在pattern或字符串以及注釋的中間用反斜杠換行)。
也可以將多個語句寫到一行中,語句之間用分號“;”隔開。AWK的模式可以是下面的其中一種:
BEGIN END /regular expression/ relational expression pattern && pattern pattern || pattern pattern ? pattern : pattern (pattern) ! pattern pattern1, pattern2BEGIN和END是兩個比較特殊的模式,他們不對輸入做模式匹配,因為BEGIN和END分別是在處理輸入之前和之后。多個BEGIN塊會被merge成一個BEGIN塊,這個BEGIN里面的語句會在開始讀取輸入之前執行;多個END塊會被merge成一個END塊,這個END里面的語句會在處理完所有輸入之后執行(或者執行exit)。注意, BEGIN和END必須是獨立的,不能糅合在其他pattern表達式中。并且BEGIN和END不能沒有action部分,也就是說BEGIN和END后面不能沒有花括號{ }。
對于/regular expression/模式,會對所有匹配到該正則表達式的record執行其關聯的語句。例如,打印包含“root”的行:awk '/root/ {print $0}' awk_test.txt一個relationalexpression可以使用后面將要講到的任何運算符,這種模式通常用來測試一個field是否匹配某個正則表達式。關系表達式是指使用關系運算符(>,>=,<,<=,==,!=)連接一個或兩個表達式組成的式子。
“&&”、“||”和“!”即我們熟知的與、或、非邏輯運算符,可以用來將多個patterns連接在一起,這時他們是short-circuite valuation(短路求值)的,例如對于&&操作,只要第一個值是false,那整個表達式就是false,就沒必要計算后續的值了。圓括號( )可以改變運算符的運算順序。
例如,打印第一個field不是“root”的行且第三個field等于“0.0”的行:awk '$1 != "root" && $3 == "0.0" {print}' awk_test.txt“?:”運算符和C語言中的三目的條件運算符一樣,pattern1為true則選用pattern2,否則選用pattern3。
pattern1, pattern2形式的表達式被稱為范圍樣式,它將匹配pattern1的record,匹配pattern2的record以及這兩個record之間的所有record都匹配出來。正則表達式
正則表達式由下表中一系列的字符集合組成。注:其中c表示character,r表示regular expression。
字符 | 含義 |
c | 匹配非元字符c |
/c | 匹配元字符c的字面含義。由于元字符有特殊含義,因此必須加反斜杠來匹配其原始的字符含義。這些字符包括"["、"]"、"/"、"^"、"$"、"."、"|"、"?"、"*"、"+"、"{"、"}"、"("、")"等。 |
. | 匹配一個字符(包括換行) |
^ | 匹配字符串的開頭 |
$ | 匹配字符串的結尾(我系統上不支持) |
[abc...] | 匹配字符列表abc…中的任何一個字符 |
[^abc...] | 匹配字符列表abc…之外的任何一個字符 |
r1|r2 | 匹配r1或r2 |
r1r2 | 匹配r1且其后緊跟r2 |
r+ | 匹配一個或多個r |
r* | 匹配0個或多個r |
r? | 匹配0個或1個r |
(r) | 分組,匹配圓括號中的所有字符組合r |
r{n} r{n,} r{n,m} | 花括號內有1個或2個數值被稱為區間表達式:只有一個數字n,則表示前面的正則表達式r重復n次;有一個數字n和一個逗號,表示r至少重復n次;有兩個數字n和m,表示r重復n到m次。 區間表達式只有在指定--posix或--re-interval的時候才可用。 |
/y | 匹配一個單詞開頭和結尾的空串,例如‘/yballs?/y’匹配‘ball’或‘balls’,但不匹配‘football’ |
/B | 和/y相反 |
/s | 匹配空白字符,同[[:space:]] |
/S | 匹配非空白字符,同[^[:space:]] |
/< | 匹配一個單詞開頭的空串,例如//<away/匹配‘away’但不匹配‘stowaway’ |
/> | 匹配一個單詞結尾的空串 |
/w | 匹配Word-constituent的字符(包括字母、數字和下劃線),同‘[[:alnum:]_]’ |
/W | 匹配非word-constituent的字符,同‘[^[:alnum:]_]’ |
/` | 匹配一個buffer(字符串)開頭的空串,同^ |
/' | 匹配一個buffer結尾的空串,同$ |
上表中紅色標記的字符用于連接多個正則表達式,我們稱之為“正則表達式運算符”或“元字符”。
在字符串常量中有效的轉義字符,在正則表達式中同樣有效。
正則表達式通常用兩個斜杠“/ /”包裹起來作為pattern使用,我們稱之為正則表達式常量。
在中括號表達式中引用‘/’、‘] ’、‘-’或‘^’,需要加反斜杠,例如“[d/]]”匹配字符'd'或字符']'。
正則表達式的匹配遵循最左開始最長的匹配的原則,例如下面表達式的結果是"<A>bcd":
echo aaaabcd | awk '{ sub(/a+/, "<A>"); print }'
字符類是POSIX標準中引入的特性,一個字符類表示了某個特定屬性的字符的集合。POSIX標準定義的字符類包括:字符類 | 含義 |
[:alnum:] | 字母或數字 |
[:alpha:] | 字母 |
[:blank:] | 空格或tab |
[:cntrl:] | 控制字符 |
[:digit:] | 數字 |
[:graph:] | 可打印且可見的字符(例如space可打印但不可見,而字符a既可打印又可見) |
[:lower:] | 小寫字母 |
[:print:] | 可打印的字符(非控制字符) |
[:punct:] | 標點符號(非字母、數字、控制字符和空白字符) |
[:space:] | 空白字符(例如空格、tab、換頁符等等) |
[:upper:] | 大寫字母 |
[:xdigit:] | 十六進制數字 |
[root@ubuntu]awk_test:$ awk '/[n[:digit:]]/' regexp.txt我們有時將匹配字母和數字寫成/[A-Za-z0-9]/,但在本地化的時候,一些國家的字母表的字符集并不是這些字符,這時我們使用/[[:alnum:]]/就可以準確的匹配而不必擔心本地化后的差異。
還有兩個特殊的字符序列:Collating Symbols和Equivalence Classes,應用在非英語國家的本地化,例如用一個字符來表示多個字符/[[.ch.]]/或者定義多個等價字符/[[=e=]]/,有興趣的可以自己查閱相關資料。
注意,/y,/B,/s,/S,/<,/>,/w,/W,/`和/'這些正則表達式的運算符是gawk特有的,gawk的不同選項可能影響如何解析正則表達式:
無選項:上述所有運算符均有效(區間表達式除外)。
--posix:只支持POSIX標準(包括區間表達式)的,那么/w就只表示字面意思的'w'。
--traditional:只支持傳統的UNIX awk的正則表達式,此時,GNU的運算符、區間表達式、字符類都不支持,八進制和十六進制的轉義字符("/ddd","/xhex")也會按字面意思解析。
--re-interval:支持區間表達式(即使有--traditional選項)。
我們可以通過將IGNORECASE設置為一個非0的值,使AWK在匹配時忽略大小寫。也可以用toupper()/tolower()或中括號表達式,來只對某一些規則忽略大小寫。動作(actions)
AWK中的動作語句(action statements)都放在花括號{ }中,由大部分語言都有的賦值、條件和循環語句組成。AWK中的運算符、控制語句以及輸入輸出語句都是仿照C語言來定義的。
運算符
AWK中運算符按照優先級由高到低,列舉如下:
運算符 | 含義 |
(…) | 分組 |
$ | field引用 |
++ -- | 自增、自減,均可前置和后置 |
^ | 冪運算(也可以用**,對應的冪運算賦值**=) |
+ - ! | 一元運算符的+、-、邏輯非 |
* / % | 乘、除、取模 |
+ - | 加、減 |
space | 字符串連接 |
| |& | 管道IO、協同進程的管道IO。getline,print和printf使用 |
< > <= >= != == | 關系運算符 |
~ !~ | 正則表達式的匹配、不匹配,一般用法是/exp/ ~ /regexp/,其中右側可以是任何表達式(不需要必須是正則表達式常量)。例如$0 ~ /foo/意為查找匹配foo的$0,若匹配,則返回1,不匹配返回0。 |
in | 獲取數組成員 |
&& | 邏輯與 |
|| | 邏輯或 |
?: | 條件表達式,格式為expr1 ? expr2 : expr3,如果expr1為true,則表達式的值為expr2,否則為expr3 |
= += -= *= /= %= ^= | 賦值運算符,均支持a=a+b和a+=b這兩種形式。 |
控制語句列舉如下:
if (condition) statement [ elsestatement ] while (condition) statement do statement while (condition) for (initialization; condition; increment) statement for (var in array) statement switch (expression) {case value or regular expression: case-bodydefault: default-body} break continue delete array[index] delete array exit [ expression ] { statements }這些控制語句和ANSI C中類似語句的語法相同。Switch的每個case分支通常要添加break語句以保證唯一匹配。
輸入輸出語句列舉如下:
I/O statement | 解釋 |
close(file [, how]) | 關閉文件、管道或協同進程。參數“how”只有在關閉協同進程的雙向管道其中一端時才會用到,是字符串類型,可取"to"或"from"。 |
getline | 把$0賦值為下一個輸入的record。同時會更新NF、NR和FNR。 |
getline <file | 把$0賦值為文件file的下一個record。同時會更新NF。 |
getline var | 把變量var賦值為下一個輸入的record。同時會更新NR和FNR。 |
getline var <file | 把變量var賦值為文件file的下一個record。 |
command | getline [var] | 執行command并將輸出作為getline或getline var的輸入。 |
command |& getline [var] | 執行協同進程command并將輸出作為getline或getline var的輸入。(協同進程是gawk的擴展,command也可以是一個socket) |
next | 停止處理當前的record,并讀取下一個record,然后重新從awk程序中第一個pattern開始處理新的record。如果當前已經達到輸入數據中最后一個record,則開始執行END{ }程序塊。 |
nextfile | 停止處理當前的文件,并從下一個文件中讀取record,然后重新從awk程序中第一個pattern開始處理新的record。FILENAME和ARGIND參數會被更新,FNR被重置為1。如果當前已經達到輸入數據中最后一個record,則開始執行END{ }程序塊。 |
打印當前的record(根據ORS的值判斷record是否輸出完畢)。 | |
print expr-list | 打印表達式列表。如果有多個表達式,每個表達式之間用OFS分隔。(根據ORS的值判斷record是否輸出完畢) |
print expr-list >file | 打印表達式列表到文件。如果有多個表達式,每個表達式之間用OFS分隔。(根據ORS的值判斷record是否輸出完畢) |
printf fmt, expr-list | 格式化的打印 |
printf fmt, expr-list >file | 格式化的打印到文件。例如: awk 'BEGIN{printf "%#x", 10 >"getnum.txt"}' |
system(cmd-line) | 執行命令cmd-line,并將exit status作為返回值。例如: system("uname –a") |
fflush([file]) | 清空已打開的輸出文件或管道文件的所有緩存。如果不帶file參數,則清空標準輸出,如果file參數為null string,則所有打開的輸出文件和管道的緩存都會被清空。 |
另外,print和printf也允許以下形式的輸出重定向:
print ... >> file | 將輸出追加到文件file |
print ... | command | 向管道寫內容 |
print ... |& command | 向協同進程或socket發送數據 |
getline命令執行成功返回1,到達文件結尾返回0,出錯返回-1。如果出錯,則字符串ERRNO包含了出錯信息。
注意,如果在打開一個雙向(two-way)socket的時候失敗,將會返回一個非致命的錯誤到調用者。
如果在一個循環中使用管道、協同進程或socket(向getline輸出或從print/printf輸入),你必須使用close()來創建新的command或socket的實例。AWK在管道、協同進程或socket返回EOF的時候不會自動關閉它們。AWK里的printf語句和sprintf()函數支持如下的標準格式轉換符:
格式符 | 含義 |
%c | 一個ASCII字符。如果傳入的參數是一個數值,則將其轉換為字符并打印;否則,認為傳入的參數是字符串,只打印該字符串的第一個字符。 |
%d, %i | 十進制數字(整數部分) |
%e, %E | 將一個浮點數以“[-]d.dddddde[+-]dd”的形式打印。%E使用大寫的E。 |
%f, %F | 將一個浮點數以“[-]ddd.dddddd”的形式打印。使用%F(需要系統庫支持),則以大寫字母顯示"not a number"和"infinity"這類的值。 |
%g, %G | 根據實際情況轉換為%e或%f,選擇其中最簡短的格式。%G對應%E。 |
%o | 無符號的八進制整數。 |
%u | 無符號的十進制整數。 |
%s | 字符串。 |
%x, %X | 無符號的十六進制整數。 |
%% | 字符'%'的原意(不會取實參)。 |
在這些格式符中,'%'和控制字符之間可以放置如下的額外參數(和C語言中printf的格式符用法相同):
count$ | 位置說明符,指定對第count個參數進行格式化轉換。例如:printf("%2$d/n", 23, 24);打印的值是24。 |
width | 指定格式化后的最短打印長度,如果不夠長則填充空格,默認右對齊。 |
- | 左對齊,通常與width搭配使用。 |
0 | 上述不足width的話,使用前導0填充而不是空格。只對數值類型有效。 |
+ | 對數值添加正負號。只對數值類型有效。 |
# | 以另一種形式打印格式化的內容:例如%#o會在數值前面填0;%#x、%#X會在數值前面填0x或0X;對%#e、%#E、%#f和%#F,結果總會有小數點;%#g、%#G總會顯示小數部分。 |
space | 對正數,前面填一個空格;對負數,前面填負號。 |
.prec | 對%e、%E、%f和%F,指明小數部分的最大位數;對%g、%G,指明有效數字的最大長度;對%d、%o、%i、%u、%x和%X,和面前的0width一樣,指明最短打印長度,不足則前面補0;對%s,指明最長打印的字符數。 |
printf("%1$*2$s/n", "Bye bye!", 12);這個例子中,1$仍然是位置說明符的作用,而*2$的意思是將第二個參數替換在這個位置,這個語句就變成了:printf("%1$12s/n", "Bye bye!", 12);特殊文件
在通過print、printf、getline進行I/O重定向時,gawk可識別一些特定的文件名,并且支持從gawk的父進程(通常是shell)中繼承這些文件的文件描述符來進行文件操作。同時,這些文件也可以用作命令行中的數據文件名。這些文件為:
/dev/stdin 標準輸入
/dev/stdout 標準輸出
/dev/stderr 標準錯誤輸出
/dev/fd/n 與文件描述符n關聯的文件
另外,使用破折號“-”也可以引用標準輸入,例如:
some_command | awk -f myprog.awk file1 - file2這條命令中,awk會先讀取file1,然后讀取some_command的輸出,然后讀取file2。
下面三個特殊的文件名,可以與協同進程操作符“|&”配合使用,創建TCP/ip網絡連接。
/inet/tcp/lport/rhost/rport 本地端口是lport,與遠程主機rhost、遠程端口rport的TCP/IP連接,如果端口為0,則讓系統自己選擇端口。
/inet/udp/lport/rhost/rport 同上,只是TCP改為UDP。
/inet/raw/lport/rhost/rport 未使用,供后續擴展。
下面這些文件名用來獲取當前正在運行的gawk進程的信息:/dev/pid,/dev/ppid,/dev/pgrpid和/dev/user。目前已被棄用,改為使用PROCINFO獲取進程信息。數值操作函數
AWK提供以下一些內置的算術函數:
函數 | 作用 |
atan2(y, x) | 求y/x的反正切,單位是弧度。 |
cos(expr) | 求余弦,expr的單位是弧度。 |
exp(expr) | 求指數。 |
int(expr) | 截斷expr保留整數部分。 |
log(expr) | 求自然對數。 |
rand() | 生成一個隨機數N,0 ≤ N < 1。 |
sin(expr) | 求正弦,expr的單位是弧度。 |
sqrt(expr) | 求平方根。 |
srand([expr]) | 為隨機數生成器指定一個新種子expr。如果不指定expr,就使用時間作為種子。函數的返回值是之前的種子。 |
Gawk提供以下內置的字符串函數:
函數 | 作用 |
asort(s [, d]) | 給數組s中的成員排序(按照gawk默認的升序排序方法),排序完成后,s的數組下標改為從1開始的整數序列。 如果指定第二個參數d,則排序的結果放在數組d中,原數組s不變化。 函數的返回值是原數組s的元素個數。 |
asorti(s [, d]) | 給數組s的下標排序,(按照gawk默認的升序排序方法),排序完成后,s的數組下標改為從1開始的整數序列,而原來的下標改為數組元素的值。原來的數組元素的值被丟棄。 如果不想丟棄原來的元素的值,可以指定第二個參數d,則排序的結果放在數組d中,原數組s不變化。 函數的返回值是原數組s的元素個數。 |
gensub(r, s, h [, t]) | 對原始字符串t,將匹配正則表達式r的子串替換為s。如果字符串h以’g’或’G’開頭,則所有匹配都替換,否則只替換第一個匹配。函數的返回值即為執行替換后的字符串,也就是說,原始字符串t不會被修改。 如果不指定參數t,就從當前record中讀取,即$0。 在s中,可以通過/1~/9來指代r中第n個圓括號中的匹配項,參考下面的例子1。/0或'&'則表示整個匹配的內容。 |
gsub(r, s [, t]) | 對原始字符串t,將匹配正則表達式r的子串全部替換為s。函數的返回值為匹配到的子串的個數,也就是說,原始字符串t會被修改。 如果不指定參數t,就從當前record中讀取,即$0。 在s中,'&'則表示整個匹配的內容。如果要書寫字符’&'的原意,要寫作"//&"。 |
index(s, t) | 返回字符串t在字符串s中第一次出現的位置。例如index(“abcdefg”, “def”)返回4,即index從1開始。如果沒找到子串t,則返回0。 |
length([s]) | 返回字符串s的長度,如果沒有參數s,則返回$0的長度。也可以傳入一個數組,這時返回數組元素的個數。 |
match(s, r [, a]) | 在字符串s中匹配正則表達式r,匹配成功則返回子串的位置(index從1開始),并更新RSTART和RLENGTH,匹配失敗則返回0; 如果有第三個參數a,則正則表達式r中每個圓括號的匹配內容會被依次賦值給數組a[1]~a[n],而整個的匹配內容則賦值給a[0]。同時,a[m, "start"]和a[m, "length"]兩個數組下標成員的值為a[m]在s中的位置和字符串長度。 見下面的例子2。 注意,如果在調用match()之前數組a不為空,則會先清空a。 |
split(s, a [, r]) | 將字符串s按照正則表達式r作為分隔符來分割,分割成的每個field保存在數組a中,函數返回fields的數量。 如果沒有r參數,則根據FS來分割。 注意,如果在調用split()之前數組a不為空,則會先清空a。 |
sprintf(fmt, expr-list) | 根據格式fmt打印表達式列表expr-list,最終的字符串作為返回值。例如: sprintf("name%d:%s, name%d:%s", 1, "George", 2, "Tim"); |
strtonum(str) | 將字符串轉換為數值,可識別八進制(以0開頭)和十六進制(以0x或0X開頭)。例如strtonum(“34”),strtonum(34.50),strtonum(“017”)。 |
sub(r, s [, t]) | 同gsub,但只替換第一個匹配的子串。 |
substr(s, i [, n]) | 獲取字符串s中,從第i個字符開始的n個字符形成的子串,該子串作為返回值。參數i從1開始。 |
tolower(str) | 將str中的大寫字母都轉換為小寫字母后作為函數返回值。 |
toupper(str) | 將str中的小寫字母都轉換為大寫字母后作為函數返回值。 |
注意,gawk3.1.5支持多字節的字符,這意味著index()/length()/substr()/match()都是針對字符起作用而不是字節。
例子1,匹配“me again”,然后將其中的me改為her:[root@ubuntu]awk_test:$ awk 'BEGIN{result=gensub("(me) (again)", "her //2", "g", "tell me again, you go again"); print result}' awk_test.txt tell her again, you go again前面在sed命令中也講到過這種用法,不過這里圓括號不需要加反斜杠轉義,并且"/2"要寫成"//2"。
例子2,測試帶第三個參數的match函數:[root@ubuntu]awk_test:$ awk 'BEGIN{sstr="name1:George, name2:Tim";pos=match(sstr, "name[1-9]:([a-zA-Z]*), name[1-9]:([a-zA-Z]*)", list); /print pos; print RSTART" "RLENGTH; for (i=0; i<=2; i++) print list[i], list[i, "start"], list[i, "length"];}' awk_test.txt11 23name1:George, name2:Tim, 1 23George 7 6Tim 21 3例子3,split函數測試:[root@ubuntu]awk_test:$ awk 'BEGIN{sstr="name1:George, name2:Tim, name3:Jason";fcnt=split(sstr, list, "name[[:digit:]]+:"); print fcnt; /for (i=0; i<=fcnt; i++) print list[i];}' awk_test.txt時間函數
由于AWK主要的用處就是處理包含時間戳信息的大型日志文件。因此AWK提供了以下時間函數來獲取和轉換時間戳:
mktime(datespec) 將datespec轉換為相對于1970-01-01零點的秒數。參數datespec的格式為:“YYYY MM DD HH MMSS[ DST]”,其中夏令時標志DST是可選的。例如mktime("2017 2 6 22 54 00")即為2017年2月6日22時54分0秒,注意這個時間是算上時區的。舉例來說,在東8區,mktime("19701 1 8 0 1")的返回值為1。
strftime([format [, timestamp[, utc-flag]]]) 將timestamp轉換為format指定的格式,timestamp需為相對于1970-01-01零點的秒數。如果utc-flag不為0或null,則轉換結果是UTC時間,否則是本地時間。如果不帶timestamp參數,則使用當前時間;如果不帶timestamp和format參數,則format默認與date命令結果的格式相同,format參數可用的格式請參考C語言中的strftime()函數,也可參考Effective AWKProgramming[2]中的說明。
systime() 將當前時間轉換為相對于1970-01-01零點的秒數。位操作函數
Gawk3.1開始的版本,提供了下面的位運算函數:
函數 | 作用 |
and(v1, v2) | 按位與 |
compl(val) | 按位取反,同C語言中的'~'運算符 |
lshift(val, count) | val左移count位,即val << count |
or(v1, v2) | 按位或 |
rshift(val, count) | val右移count位(高位補符號位),即val >> count |
xor(v1, v2) | 按位異或 |
AWK中可以自定義函數,形式如下:
function name(parameter list) { statements }
或
func name(parameter list) { statements }
其中,name是函數名,前面添加關鍵字function或func。圓括號內是形參列表,后面大括號內書寫函數的實現。
例如下面的例子:function add_INT(a, b){ return (a+b);}function add_ARRAY(array){ sum = 0; for(i in array) { sum += array[i]; } return sum;}BEGIN {aint[0]=10; aint[1]=11; aint[2]=12;print add_ARRAY(aint); print add_INT(12, 78);}注意,對于自定義函數,在調用函數時,函數名和左圓括號之間不能有空格(AWK的內置函數沒有這個限制)。
在AWK中,所有的變量都是全局的,那么就可能出現一個函數中的局部變量和主程序的變量重名。如果想避免這種情況,可以在函數的參數列表里指明局部變量,方法是將局部變量寫在形參后面,并且用多個空格與形參列表分開。
例如:function test(a, b, c){ c = 20; sum = a + b;}BEGIN {c = 10;sum = 5;test(11, 22);print c" "sum;}這個例子中,有c和sum兩個變量,在函數test中,會修改c和sum,但是由于指明了c在test()中是作為局部變量的,因此不會影響主程序中c的值。所以print打印出的結果是“10 33”。
實際上,參數列表里的形參都會被認為是局部的,用多個空格分開的做法只是為了代碼的可讀性。
當需要將一段通用代碼制作成庫函數時,自定義函數就派上了用場,可以配合--source選項來在命令行程序中引用庫函數。
一些建議:在自定義函數內部,盡量避免定義外部可能使用的變量,例如“i”,“j”這樣的變量,在外部程序中很可能用到,因此在函數內部就不要用這樣的變量命名。建議在函數內變量命名時以“_”開頭來避免沖突。另外,變量和函數的命名盡量體現它的作用和含義。最后,如果函數內定義了外部可以使用的全局變量,變量名可以第一個字母大寫,如“Optind”以和局部變量區別(不全部大寫是為了防止和AWK內置變量混淆)。
可以參考Effective AWK Programming[2]中第10章的部分庫函數的實現和第11章的自定義函數舉例來學習自定義函數的寫法。信號
pgawk可接收SIGUSR1和SIGHUP兩個信號。SIGUSR1信號會使pgawk生成profile文件(如之前--profile所述),包含自定義函數的調用棧,之后pgawk程序繼續運行。SIGHUP信號同樣會產生profile文件,之后pgawk程序退出。
返回值
Gawk執行正確返回EXIT_SUCCESS,通常是0;執行失敗返回EXIT_FAILURE,通常是1;如果發生嚴重錯誤,返回2,但有些系統上會返回EXIT_FAILURE。
如果exit語句指定了返回值,則gawk返回這個指定的值。參考資料
[1] Gawk(1) manpage for GNU Awk 3.1.8
[2] Effective AWK Programming: http://www.gnu.org/software/gawk/manual/gawk.html
新聞熱點
疑難解答