因爲項目經歷緣由,常用awk處理一些文本數據。甚至,我特地下載了一個windows上的awk:gawk.exe,這樣在windows上也能享受awk處理數據的方便性,。c++
俗話說,「常在河邊走,哪能不溼鞋」,使用awk過程當中碰上過很多坑,這裏稍總結一下,但願對你們有幫助。正則表達式
1 FS問題
看看這兩個awk腳本:shell
cat demo_1.txt demo_2.txt 1|2|3|4| 1|@|2|@|3|@|4|@| awk -F '|' '{print $2}' demo_1.txt; # 腳本1 awk -F '|@|' '{print $2}' demo_2.txt; # 腳本2
腳本原目的是達到的目的是分別按'|'和分隔'|@|',輸出demo.txt第二列。但實際上,第一個腳本這樣寫沒錯,但第二個腳本倒是錯的。express
爲何呢?windows
由於豎線在正則表達式中是一個特殊字符,表示匹配豎線左右的字符組之一。若是想使用豎線自己,須要對用轉義符。數組
但爲何第一個命令也同樣使用了豎線卻沒有問題呢?數據結構
這就涉及到awk在一個規定:函數
若是FS設置了不止一個字符做爲字段分隔符,將做爲一個正則表達式來解釋,不然直接按該字符作爲分隔符對每行進行分割。編碼
因此第一個命令使用了豎線作分隔符沒問題,第二個命令就出錯了。spa
2 正則表達式與反斜槓號問題
繼續上面的問題討論,若是demo.txt是按"|@|"作爲分隔符的,要輸出demo.txt第二列,正確的答案應該是怎麼寫呢?
答案是:
awk -F '\\|@\\|' '{print $2}' demo.txt;
注意這裏,FS的值是'\\|@\\|',而不是簡單的'\|@\|'(這樣寫會報錯,提示:awk: 警告: 轉義序列「\|」被看成單純的「|」)。爲啥要這樣寫呢?
先來看一個試驗:
echo|awk -F '|@|' '{print FS}' # 腳本1 echo|awk -F '\|@\|' '{print FS}' # 腳本2 echo|awk -F '\\|@\\|' '{print FS}' # 腳本3
能夠看到第一和第二個腳本,FS值是同樣的。緣由是awk先要解析用戶輸入的字符串,並將解析結果賦值給FS,而後再調用split類函數,把FS當成函數參數傳進去。
而split須要再對FS進行一次解析,編譯成正則表達式。awk解析字符串給FS變量賦值時會把'\|'認爲是'|',從而致使傳進split函數時,分隔符已。
所以,若是想讓awk正確分割記錄,須要使FS='\\|@\\|',這時awk會把\\解析成轉義字符'\',這樣豎線就能被當普通字符處理國。
3 關聯數組訪問問題
曾經碰上過這樣一個場景:文件a.txt包含少許用戶餘額(userid|amt),約100行記錄,文件b.txt包含了全部用戶的餘額(userid|amt),約有100萬行記錄。
如今要求關連a.txt和b.txt(使用userid),找出在a.txt與b.txt都存在的userid,並輸出其中b.amt大於a.amt的記錄。
當時我先寫了如下腳本:
awk -F '|' 'BEGIN{ while(getline < "a.txt") { v_user_map[$1] = $2; } } { v_amt_a = v_user_map[$1]; if ((v_amt_a != "") && v_amt_a < $2) print $0; }
看起來邏輯彷佛沒有問題,因而開始跑。可是跑起來發現效率遠比本身想象的低,並且發現程序運行過程消耗的內存愈來愈多。
這明顯是有問題的,理論上應該是BEGIN那段語句會消耗一些內存,以後應該就不須要再消耗纔對。
因爲寫過c++代碼,裏面也有相似關聯數組的數據結構,我很快猜想並實驗證實緣由:v_amt_a = v_user_map[$1]; 這一句。
雖然這裏沒有給v_user_map[$1]賦值,可是awk會默認賦值爲空,致使v_user_map數組元素愈來愈多,佔用內存空間愈來愈大,查找效率愈來愈低。
知道問題就好解決了,查了一下awk幫助手冊,發現能夠這樣寫:
awk -F '|' 'BEGIN{ while(getline < "a.txt") { v_user_map[$1] = $2; } } { if ($1 in v_user_map) { if (v_user_map[$1] < $2) print $0; } }
使用in操做符來判斷元素是否在關聯數組裏面,這樣就不會有默認賦值。
4 內存限制問題
若是awk是32位程序(可使用file命令判斷),那麼上面的腳本1,極可能跑着跑着就core了。由於默認狀況下,32位的awk最多隻能消耗256M內存。
若是申請內存超過這個數就會發生異常退出。
解決方法是使用64位程序,或者修改環境變量「export LDR_CNTRL=MAXDATA=0x80000000」。(AIX4.3以上有效)
5 getline返回值問題
注意樓上的getline用法,while(getline < "a.txt")循環讀取文件直到結束。這樣寫實際上是不太規範的,有隱患。
曾經我覺得getline讀到文件尾會把$0置空,後來實踐發現實際不是這樣的。geline在碰上文件尾時會返回0,但$0仍是保持最後一行的記錄不變。因而就改爲這種寫法。
不過這種寫法,有時也會碰上問題,緣由:getline返回值有三種狀況:1 正常讀取到一條記錄 0 達到文件尾 -1 文件不存在或其它錯誤。
若是a.txt不存在,getline會返回-1,致使死循環。我之前曾經碰上過由於這個緣由致使程序掛死,因此特別提出來讓你們注意。
建議你們使用函數前最好先看看幫助文檔裏面關於函數描述。
6 管道問題
先來看這個腳本:
ls -1rt demo.txt list.txt echo -e "\n\n" | awk '{ while("ls -1rt" | getline) { print NR " : " $0 > "list.txt";}}'
猜猜看:腳本運行完後list.txt裏面的內容是什麼?
答案:
cat list.txt 1 : demo.txt 1 : list.txt
相信有很多朋友會以爲詫異:
有些人會認爲list.txt裏面應該只有一行數據,就是ls -1rt命令輸出內容的最後一行。
有些人會認爲應該有6條數據纔對,由於ls -1rt執行了三次。
有這種想法的人,多半是不知道awk一個規定: 默認狀況下同一個文件或者管道只打開一次,若是須要重複打開,須要先close。
上面的腳本因爲沒有顯式close文件和管道,list.txt和ls -1rt都只打開/執行了一次,因此輸出結果如上。
再猜猜看:下面這個腳本運行完後list.txt裏面的內容是什麼?
echo -e "\n\n" | awk '{ while("ls -1rt" | getline) { print NR " : " $0 > "list.txt";} close("list.txt"); close("ls -1rt");}'
7 輸出單引號問題
你們知道,awk腳本通常是用單引號括起來的,形如:awk '{ print "do something"; }' 。
所以,在awk中要使用單引號是比較麻煩的事情。網上找awk輸出單引號通常能夠找到如下方法:
echo | awk '{ print "'\''"; }'
不少人所以就誤會了,覺得awk腳本因爲使用了單引號作爲腳本開始結束標誌,因此在awk腳本里面是不能直接使用單引號的。
其實這是誤會了,看下面的腳本你就知道。
cat demo.awk { print "'"; } echo | awk -f demo.awk '
可見,awk腳本是能夠直接使用單引號的,也不須要使用單引號把腳本括起來。 之因此在命令行須要用這麼彆扭的寫法,是由於shell的關係:使用單引號括起來的內容,不會被shell當成特殊字符處理。
由於awk腳本里面常常須要$n來獲取第幾個字段的內容,而$在shell裏面是有特殊意義的,表明變量開始。 若是不用單引號括起來,就會出問題。
'{ print "'\''"; }' 這段能夠這樣理解:腳本分三段
1、 '{ print "' ; 2、 \'; 3、 '"; }';
每段被shell解析後是這樣的
1、 { print " ; 2、 '; 3、 "; } ;
三段合起來就是傳給awk的腳本內容:{ print "'"; }。理解了這個以後,在windows使用awk碰上如下問題,你就知道怎麼解決了:
C:\Users\hch>awk '{print "";}' awk: '{print awk: ^ invalid char ''' in expression
8 自動隱式轉換問題
在c語言裏面,咱們習慣了整數相除,結果仍是整數。因此5/2結果是2,不是2.5。
然而在awk裏面,因爲沒有明確指定變量類型,因此在變量計算過程常常會發現隱式轉換,整數相除結果多是小數。
舉例:
echo | awk '{v_result = 5 / 2; print v_result}' 2.5
若是咱們想要實現c語言的整數相除效果,要怎麼辦呢? 可使用int函數,以下:
echo | awk '{v_result = int(5 / 2); print v_result}' 2
9 中文豎線問題
實際工做中,常常碰上文件中每行記錄裏面用豎線'|'作爲分隔符的,如"a|b|c|d"。若是文件裏面沒有中文,這樣作是沒問題的。
但若是有中文,特別是gbk編碼在中文時,這樣作就容易出問題了。
gbk編碼中,中文由兩個字節組成,第一個字節取值範圍是[128, 256),第二個字節取值範圍是[0, 256)。
若是第二個字節值正好是'124',也就是'|'字符的asscii碼,awk處理時就會誤覺得這個字節是分隔符,從而致使分割字符串時出現錯亂。
那有哪些中文是這樣的呢? 能夠用如下腳本輸出gbk編碼中包含豎線的特殊中文:(其它編碼相似)
echo|awk '{for(i = 128; i < 256; i++) { printf("%c| ", i); } }' #終端編碼要是GBK
€| 亅 倈 億 剕 厊 啢 噟 坾 墊 妡 媩 寍 峾 巪 弢 恷 憒 抾 搢 攟 晐 東 梶 榺 檤 殀 泑 渱 潀 瀨 焲 爘 ▅ ﹟ 獆 珅 瑋 瓅 畖 瘄 皘 眧 瞸 硘 磡 祙 秥 穦 竱 箌 簗 粅 紎 絴 緗 縷 纜 羭 聕 脇 膢 舼 苵 莬 葇 蓔 蕓 藎 蘾 蛗 蝲 蟶 衸 褆 襹 觸 詜 諀 謡 讄 貄 質 趞 踻 軀 輡 迀 遼 鄚 醸 鈢 銃 鋦 鍇 鎩 鐋 鑭 閨 陓 雦 靯 韡 顋 飢 饇 駖 騶 髚 魘 鮸 鰘 鱸 鴟 鵿 鷟 鹼 鼃 齶
碰上這種狀況暫時我沒有發現太好的處理方法,建議使用比較長的分隔符,減小碰上問題的機率,如'|@|'。
若是分隔符不可變,那能夠考慮使用iconv轉換編碼,處理完後再轉換回來。
10 函數名與變量名衝突
awk內置了不少函數,若是不當心把變量名字取得跟這些函數名字同樣,程序就會報錯。提示很不清楚,就只是說錯了,不說緣由,特別坑。
例如如下這個報錯:
awk '{ if (NR == FNR) { sub[$1] = $2; } else { print sub[$1]; } }' subsid_amt.txt subsid.txt awk: { if (NR == FNR) { sub[$1] = $2; } else { print sub[$1]; } } awk: ^ syntax error
因爲這個腳本是晚上加班到深夜時寫的,當時頭腦不清醒,看到報錯蒙了很久:怎麼看語法都是對的,可是運行卻老是提示語法錯了。
因此如今我寫比較複雜的awk腳本,變量名都習慣前面加上v_後綴,這樣能夠減小名字衝突的機率。
暫時就總結了這些。若是你們也碰上過使用awk的問題,不妨一塊兒發出來討論一下吧:)