導論:awk的世界css
嚴格地說,awk應該算是獨立於shell以外的一門編程語言了。因爲開發人員可以在shell command line內直接編寫awk代碼,並整合到管道中,所以,awk和shell整合地很是緊密,變成了一項標準unix tool。awk尤爲擅長處理文本設計、編排報表的工做,經常會帶給你意外的驚喜。如下awk是linux的gawk版本摘要,awk遵循POSIX規範。
注:如下的記錄(record)指一行數據,字段(field)指記錄中以分隔符分開的一段數據。
基礎語法
- awk options 'pattern { action }' input-files
- #或
- awk options -f awk-scripting-file input-files
awk一次讀取輸入流(文件或標準輸入)的一條記錄(record,一般是行),針對每一個成功匹配這一行的模式(模式值爲真),awk執行對應的action,且全部模式都會針對每條記錄檢查。如此往復,直到EOF。因此pattern和action暗含了一個if..then..的控制結構。
- pattern部分用來定位具體須要action的行:幾乎是任何表達式,通常狀況下,是一個ERE正則。pattern省略後,即action對每一行都會生效。
- actions部分用來執行具體操做:爲任何awk語句,通常狀況下,是一個print $n語句。action省略後,action即{ print },即打印整行。
awk內建變量
$0 : 當前整條輸入(行)記錄,不包括RS(\n)字符;
$n : 當前行記錄的第n個字段,字段間用FS分隔;
ARGC : 命令行參數的數目
ARGV : 命令行參數的數組
ARGIND : 當前文件的位置(從0起始)
CONVFMT : 數字轉換格式(默認%.6g);
ENVIRON : 調用awk的環境變量hash數組;
ERRNO : 系統錯誤描述;
FIELDWIDTHS : 字段寬度列表(用空格分隔);
FILENAME : 當前文件名;
IGNORECASE : 若爲真,則忽略大小寫匹配;
OFMT : 數字輸出格式(默認%.6g);
OFS : 輸出字段分隔符,默認是一個空格字符,output field separator
ORS : 輸出記錄分隔符,默認是換行符,output record separator
RS : 輸入記錄分隔符,默認是換行符,record separator
FS : 輸入字段分隔符(默認是任意空格、\t字符組合),field separator,能夠設置成/ERE REGEX/;
NR : 當前總記錄數, number of records
NF : 當前記錄中的字段數, number of fields
返回值0 : 表示失敗,假
返回值非0 : 表示成功,真
註釋和空白
- 註釋爲#符號開頭,類比shell;
- 任何地方容許空白,單條語句不能多行,除非結尾有'\',類比shell;
標量:字符串和數字
awk標量存放字符串和數字,賦值即聲明,變量大小寫敏感。
- awk字串最短爲空,長度爲0,最長無限制,視內存而定。
- 字串的比較用"==,!=,<,>,>="比較,非零爲真,短的字符串總<長字符串,如"a"<"aa"返回1;
- awk沒有特殊的字符串接續操做符,類比shell,兩個字串按照字面的排列合在一塊兒。
- awk用一概在內部用雙精度浮點數表示任何數值;
- #對於第三點,字符串相連
- $ awk 'BEGIN{ s="ab" "cd" ; q="xy" "z" ; s=s q ; print s }'
- abcdxyz #這裏s q空格必須
正則
awk使用ERE(擴展posix正則表達式),用""或//圈引。
ERE基本元素:
\ 轉義序列
. 任意字符(不含換行符)
* 重複前一個正則匹配0到屢次
^ 錨點,行開頭
$ 錨點,行結尾
[..] 字符集,只一次匹配字符集中的一個字符;注意[^..]表明匹配不在字符集裏的字符;
{n,m} 重複n到m次,必須顯示指定awk --posix或--re-interval開啓重複n次支持
+ 匹配一個或多個
? 匹配一個或0個
| 匹配或
(..) 分組,以便\n後向引用
\w [[:alnum:]_],同perl裏的\w
\W \w的取反
\< 單詞開頭
\> 單詞結尾
\b 單詞結尾或開頭,\<,\>的結合;
數組
awk支持關聯數組,也就是說,鍵能夠是數值,能夠是字符串表達式。關聯數組也叫哈希(hash)。
awk的數組索引從1開始。
awk的數組是自動建立,自動增加的;
awk數組的元素間獨立,沒必要要爲統一類型;
數組名和變量名的命名空間是重合的(不像perl),所以變量名和數組名應避免衝突;
awk支持多維數組,awk_set[ v1, v2, v3 ]肯定一個三維數組元素;
命令行參數
ARGC是參數索引計數,ARGV是參數數組,經過遍歷,可以取得awk命令上的參數(不包含選項)
- $ awk -v external_var=ext 'BEGIN { for (k=0;k<ARGC;k++)
- print "ARGV["k"] is "ARGV[k] }' s*
- ARGV[0] is awk
- ARGV[1] is sed.sh
- ARGV[2] is standardio.sh
- ARGV[3] is subprocess.sh
- #可見第一個參數ARGV[0]始終是awk程序,選項-v沒有計入參數列表中。
環境變量
環境變量存在ENVIRON關聯數組裏,只讀就知足通常需求了。
- $ awk 'BEGIN{ print ENVIRON["LANG"] ; print ENVIRON["SHELL"] }'
- en_US
- /bin/bash
模式pattern
視模式爲測試條件,用以匹配當前行,若模式表達式的值爲真(非0),則爲真,執行action代碼。
模式有幾種形式:
BEGIN #begin模式
END #end模式
NF == 0 #空記錄
NR < 5 #選定第1-4行的文本域處理
( FNR == 3 ) && ( FILENAME ~ /[.][ch]$/ #若是當前文件是c源代碼文件,且選定第3行;
$1 ~ /jack/ #第1個字段匹配jack
$2 !~ /jack/ #第2個字段不匹配jack
/^\w*$/ #等效於$0 ~ /regex/,全由單詞字符組成的行;
( FNR == 3 ) , ( FNR == 10 ) #範圍表達式,選定第3-10行的文本域;
BEGIN和END
BEGIN和END是兩種特殊模式,這些塊,即對應的action,只運行一次。BEGIN和對應的塊一般放在起始處,作變量初始化;END和對應的塊一般放在結尾,作統計結果輸出和腳本清理;
注意:若是BEGIN模式後不包括其餘操做,awk會退出,沒必要要讀取任何輸入和文件。
- #在BEGIN塊裏實現FS和OFS的全局設置;
- #取用戶名和用戶登陸shell並用*號隔開打印
- #!/bin/bash
- awk ' BEGIN{#這裏的BEGIN是模式,{}是action
- FS=":"
- OFS="*"
- }
- { print $NF , $1 } //逆序
- END{
- #comment here
- }' /etc/passwd
-
操做action
操做由{}圈引,awk語句在一行內用";"隔開,不一樣行時不用加";"。
最多見的操做:print。一個print語句裏包含了以逗號分隔的0或多個表達式,輸出字段分隔字符由OFS指定,print結束後以ORS結尾輸出。
- #把單詞列表逐行輸出
- $ echo who is my love | awk '{ OFS = "\n" ; $1 = $1 ; print }' #$1=$1必須,至少指定一個字段的值,不然$0不會變
- who
- is
- my
- love
- -------------------------
- #打印全文,同cat file
- awk '{ print $0 }' file
- awk '{ print }' file
記錄和域
- 記錄record:由RS分隔的字串稱爲一條記錄,RS默認爲\n,即一條記錄爲一行,$0表明整條記錄;NR爲當前行的行數(總文件範圍下的);
- 域field:一條記錄的每一個字段叫「域」,NF爲當前域(字段)的個數;輸入域分隔符是FS,默認狀況下是\t和空格的組合,可經過-F命令行或FS賦值指定。域的引用:$1,$2,...,$NF。
1.選取若干指定的列:$n
- #產出PID和進程命令(ps -ef的第2列和最後一列)
- ps -ef | awk '{ print $2 , $NF }'
2.設置字段分隔字符:FS和字段輸出字符:OFS
-v 傳遞shell變量給awk程序
- #取用戶名和用戶登陸shell
- awk -F':' '{ print $1,$NF }'
- 進階
- awk -v FS=":" -v OFS="<->" '{print $1 ,$2,$NF}' /etc/passwd
-
- root<->x<->/bin/bash
- bin<->x<->/sbin/nologin
- daemon<->x<->/sbin/nologin
- -----------------------------------
- $ echo | awk -v 'a=yes' '{print a }'
- yes
awk控制語句(k&r風格)
判斷
- if ( expression1 ){
- statement1
- }else if ( expression2 ){
- statement2
- }else{
- statement3
- }
循環
while循環
- while ( expression ){
- statement
- break #可選
- }
for循環
- for( i=1 ; i<=5 ; i++ ){
- statement
- break|continue #不支持多層break
- }
for循環遍歷關聯數組
- $ awk 'BEGIN{
- hash["a"]=1
- hash["b"]=2
- hash["c"]=3
- for ( name in hash ){ #name至關於perl裏的for $_ (keys %hash)
- print name "=>" hash[name]
- }
- }'
- a=>1
- b=>2
- c=>3
awk測試數組成員
- awk 'BEGIN{
- hash["a"]=1
- hash["b"]=2
- hash["c"]=3
- if( "d" in hash ){
- print "in"
- }else{
- print "out"
- }
- }'
- --------------------------------
- #而非
- if ( hash["d"] != "" ) ... #這樣會引用hash["d"],使之當即初始化爲空值數組元素。
awk引用shell命令,引用shell變量、IO重定向及其餘
1. 管道 + getline 實現
須要注意getline函數>0時,成功;
須要及時關閉管道文件;
用while處理時,管道命令運行一次,getline每次讀取一行;
$ awk 'BEGIN{
cmd = "date"
#使用變量保存管道
if ( ( cmd | getline var ) > 0 ){
#getline從外部命令cmd的結果裏讀取一行,存入var
#測試getline返回的是否>0,成功
print "The date is " var
}
close(cmd)
#建議關閉管道文件
}'
The date is Mon Dec 17 22:41:57 CST 2012
---------------------------------------------
$ awk 'BEGIN{
cmd = "cat txt ; rm txt"
#測試該命令是否在while測試條件中只運行了一次,若爲
屢次,rm會報錯
while ( ( cmd | getline var ) > 0 ){
#一次讀取一行,getline執行屢次,cmd只執行一次
#引用shell命令的標準形式
print ">>>" var
}
close(cmd)
}'
2.
system()實現
-
- $ awk 'BEGIN{
- cmd = "uptime"
- if ( system( cmd ) != 0 ){ #推薦加調用判斷
- print "system call bad"
- }}'
- 23:08:21 up 151 days, 3:02, 5 users, load average: 0.00, 0.00, 0.00
3. IO重定向
能夠在print和printf後加入>或>>重定向語句,也能夠在getline後加入<語句,一旦使用完畢,須關閉文件句柄 close(fd)
- $ awk '{ print $0 > "youtube.txt" END{ close("youtube.txt") }' plain.txt
- $ cat youtube.txt
- send someone to love me
- i need to rest in arms
- ...
4. 引用變量
-v 傳遞shell變量給awk程序 或 "'$shell_var'"
-
- $ awk -v awk_var=$you 'BEGIN { print awk_var }'
- me
- $ awk 'BEGIN { print "'$you'" }'
- me
awk字符串函數
字長:length( str )
- $ echo "Legend" | awk '{ print length($0) }'
- 6
子串提取:substr( str , start_index , len )
- echo "Legend" | awk '{ print substr($0,4,3) }' #好吧,index是從1開始的
- end
大小寫轉換:toupper(str ), tolower( str )
- $ echo "Legend" | awk '{ print toupper($0) }'
- LEGEND
- $ echo "Legend" | awk '{ print tolower($0) }'
- legend
找字串:index( str )
若找不到,返回0,表示失敗;
- $ echo "Legend" | awk '{ print index($0,"ge") }'
- 3 #返回ge的起始index位置
匹配正則match( str , regex )
若匹配,返回匹配string的index位置,不匹配爲0
- $ echo "I'm the Legend" | awk --re-interval '{ #巨坑:awk不支持{n,m}的ere,必須制定--posix或--re-interval才能使用{n,m}的正則
- print match($0,/[a-z]{3}/)
- print substr($0,RSTART,RLENGTH )
- }'
- 5
- the
替換:sub( regexp, replacement, target )與gsub(globlly substitution),sub和gsub直接修改target字串。
- $ awk 'BEGIN{
- string = "champion12sh3ip"
- print string
- sub( /[0-9]/ , "" , string)
- print string
- }'
- champion12sh3ip
- champion2sh3ip
- --------------------
- $ #將sub換成gsub
- champion12sh3ip
- championship
- --------------------
- $ gsub( /[0-9]/ , "&&" , string) #特殊用法,&爲regex匹配的文本段,&&爲重複兩遍
- champion1122sh33ip
分割字符串:int split( string, array, regexp )
當regexp省略時,默認使用FS的值 ,split()返回array長度。
- $ awk 'BEGIN{
- string = "yes I am what i am " #最後的空白省略了,和FS處理時同樣
- len = split( string , array )
- print len
- for( i=1 ; i<=len ; i++){
- print ">" array[i]
- }
- }'
- 6
- >yes
- >I
- >am
- >what
- >i
- >am
- ---------------------------------------------
- $ awk 'BEGIN{
- string = "money:is:very:bad:" #不會省略最後一個":"
- len = split( string , array , ":")
- print len
- for( i=1 ; i<=len ; i++){
- print ">" array[i]
- }
- }'
- 5
- >money
- >is
- >very
- >bad
- >
- -----------------------------------------------
-
- $ awk 'BEGIN{ #把字串每個字符提出的好方法
- string = "I am what i am"
- len = split( string, chars, "" )
- for ( idx = 1 ; idx <= len ; idx++ ) {
- print chars[idx]
- }
- }'
-
- $
- i$
- ^I$
- a$
- m$
- ----------------------------------------
- $ split("", array) #清空數組
字串格式化printf,sprintf
printf將字符串和數字格式化爲所須要的格式,printf()打印字串到屏幕,sprintf()將格式化後的字串保存到變量。
對齊操做:
% width.precision format-specifier
- $ awk 'BEGIN{
- a="i am the"
- b="99"
- c="C"
- printf("%s : %d,%c\n", a, b, c)
- d=sprintf("%s-%d-%c",a,b,c)
- print d
- x=-7.58
- printf "%010.5f\n", x
- }'
- i am the : 99,C
- i am the-99-C
- -007.58000
awk數值函數和隨機數
awk提供了大部分經常使用的數值函數,包括cos(x),log(x),int(x)等,其中隨機數rand()是一個比較tricky的地方,rand()每次調用返回的都是相同的[0,1)區間的"隨機數",所以須要前置srand(),把當前時間設爲種子,才能保證每次rand()出來的儘量隨機(僞隨機)。
- #這個例子實現了給定n,m,輸出這個區間的隨機整數。
- awk '
- BEGIN{
- myrand = gen_random(10,20)
- print myrand
- }
-
- function gen_random( low , high ){ #函數能夠在任意成對的模式/操做組先後之間定義
- low = int( low )
- high = int( high )
- if( low >= high ){ #health check
- return low
- }
- srand()
- n = low + int( rand() * ( high + 1 - low ) ) #由於取整,+1必須,0<=rand()<1
- return n
-
- }
- '
- $ for((i=1;i<=100;i++)); do sh awk.sh; sleep 1; done
-
- 17
- 19
- 12
- 18
- 16
- 17
- ...
awk實例
說完了一些基本概念,讓咱們從一些例子體驗一下awk,某些例子前面已經舉過。
1. awk打印非空行
- $ awk 'NF > 0' file
- $ awk 'NF > 0 { print $0 }' file #等價形式
- -----------------------------------
- $ awk '{ print }' $file #同cat file
2. awk轉置單詞列表到逐行輸出
- echo $list | awk '{ OFS="\n" ; $1=$1 ; print $0 }'
3. wc的一種實現
- $ awk '{count+=length($0)+1;word+=NF } END{ print NR , word , count }' plain.txt
- #length($0)計算整個行的字符數,+1算上換行符;
4. 計算某一列的平均值(假設第一行不是字段說明)
- $ awk 'BEGIN{ col=N } { sum += $col } END{ print sum/NR }' file #N是用戶定義的列索引值
5. grep的一種實現
- $ grep -E 'pattern' files #ERE
- $ awk '/pattern/ { print FILENAME ":" FNR ":" $0 }' files #注意要用FNR,不是NR
6. dos2unix的3種實現
- $ awk 'BEGIN{ RS="\r\n" } { print }' windows_format.txt > unix_format.txt #指定RS值
- $ sed 's/\r$//' windows_format.txt > unix_format.txt #sed用法
- $ tr -d '\r' < windows_format.txt > unix_format.txt #tr用法
- $ dos2unix -n windows_format.txt unix_format.txt #dos2unix用法
- $ awk 'BEGIN{
- string = "I am what i am"
- len = split( string, chars, "" )
- for ( idx = 1 ; idx <= len ; idx++ ) {
- print chars[idx]
- }
- }'
參考資料
http://bbs.chinaunix.net/thread-448687-3-1.html
http://www.ibm.com/developerworks/cn/linux/shell/awk/awk-1/index.html
http://www.ibm.com/developerworks/cn/linux/shell/awk/awk-2/
http://www.ibm.com/developerworks/cn/linux/shell/awk/awk-3/
http://www.tsnc.edu.cn/default/tsnc_wgrj/doc/awk.htm#id2811692
http://www.cnblogs.com/serendipity/archive/2011/08/01/2124118.html
http://hi.baidu.com/wjx_5893/item/4513c5870f082aded0f8cd1e
http://blog.sina.com.cn/s/blog_5d22d9b40100jer4.html