語法形式
awk [options] 'script' var=value file(s) awk [options] -f scriptfile var=value file(s)
常用命令選項
-F fs fs指定輸入分隔符,fs可以是字符串或正則表達式,如-F:-v var=value 賦值一個用戶定義變量,將外部變量傳遞給awk-f scripfile 從腳本文件中讀取awk命令-m[fr] val 對val值設置內在限制,-mf選項限制分配給val的最大塊數目;-mr選項限制記錄的最大數目。這兩個功能是Bell實驗室版awk的擴展功能,在標准awk中不適用。模式:
/正則表達式/:使用通配符的擴展集。 關系表達式:使用運算符進行操作,可以是字符串或數字的比較測試。 模式匹配表達式:用運算符~(匹配)和~!(不匹配)。BEGIN語句塊、pattern語句塊、END語句塊。awk 'BEGIN{ print "start" } pattern{ commands } END{ print "end" }' file
一個awk腳本通常由:BEGIN語句塊、能夠使用模式匹配的通用語句塊、END語句塊3部分組成,這三個部分是可選的。任意一個部分都可以不出現在腳本中,腳本通常是被單引號或雙引號中,例如:
awk 'BEGIN{ i=0 } { i++ } END{ print i }' filename awk "BEGIN{ i=0 } { i++ } END{ print i }" filename
awk 'BEGIN{ commands } pattern{ commands } END{ commands }'
第一步:執行BEGIN{ commands }語句塊中的語句;
第二步:從文件或標准輸入(stdin)讀取一行,然後執行pattern{ commands }語句塊,它逐行掃描文件,從第一行到最後一行重復這個過程,直到文件全部被讀取完畢。
第三步:當讀至輸入流末尾時,執行END{ commands }語句塊。
BEGIN語句塊在awk開始從輸入流中讀取行之前被執行,這是一個可選的語句塊,比如變量初始化、打印輸出表格的表頭等語句通常可以寫在BEGIN語句塊中。
END語句塊在awk從輸入流中讀取完所有的行之後即被執行,比如打印所有行的分析結果這類信息匯總都是在END語句塊中完成,它也是一個可選語句塊。
pattern語句塊中的通用命令是最重要的部分,它也是可選的。如果沒有提供pattern語句塊,則默認執行{ print },即打印每一個讀取到的行,awk讀取的每一行都會執行該語句塊。
示例
echo -e "A line 1\nA line 2" | awk 'BEGIN{ print "Start" } { print } END{ print "End" }'
打印結果:
Start A line 1 A line 2 End
當使用不帶參數的print時,它就打印當前行,當print的參數是以逗號進行分隔時,打印時則以空格作為定界符。在awk的print語句塊中雙引號是被當作拼接符使用,例如:
echo | awk '{ var1="v1"; var2="v2"; var3="v3"; print var1,var2,var3; }'
打印結果:
v1 v2 v3
雙引號拼接使用:
echo | awk '{ var1="v1"; var2="v2"; var3="v3"; print var1"="var2"="var3; }'打印結果:
v1=v2=v3
{ }類似一個循環體,會對文件中的每一行進行迭代,通常變量初始化語句(如:i=0)以及打印文件頭部的語句放入BEGIN語句塊中,將打印的結果等語句放在END語句塊中。
說明:[A][N][P][G]表示第一個支持變量的工具,[A]=awk、[N]=nawk、[P]=POSIXawk、[G]=gawk
$n 當前記錄的第n個字段,比如n為1表示第一個字段,n為2表示第二個字段。 $0 這個變量包含執行過程中當前行的文本內容。 [N] ARGC 命令行參數的數目。 [G] ARGIND 命令行中當前文件的位置(從0開始算)。 [N] ARGV 包含命令行參數的數組。 [G] CONVFMT 數字轉換格式(默認值為%.6g)。 [P] ENVIRON 環境變量關聯數組。 [N] ERRNO 最後一個系統錯誤的描述。 [G] FIELDWIDTHS 字段寬度列表(用空格鍵分隔)。 [A] FILENAME 當前輸入文件的名。 [P] FNR 同NR,但相對於當前文件。 [A] FS 字段分隔符(默認是任何空格)。 [G] IGNORECASE 如果為真,則進行忽略大小寫的匹配。 [A] NF 表示字段數,在執行過程中對應於當前的字段數。 [A] NR 表示記錄數,在執行過程中對應於當前的行號。 [A] OFMT 數字的輸出格式(默認值是%.6g)。 [A] OFS 輸出字段分隔符(默認值是一個空格)。 [A] ORS 輸出記錄分隔符(默認值是一個換行符)。 [A] RS 記錄分隔符(默認是一個換行符)。 [N] RSTART 由match函數所匹配的字符串的第一個位置。 [N] RLENGTH 由match函數所匹配的字符串的長度。 [N] SUBSEP 數組下標分隔符(默認值是34)。
示例:
echo -e "line1 f2 f3\nline2 f4 f5\nline3 f6 f7" | awk '{print "Line No:"NR", No of fields:"NF, "$0="$0, "$1="$1, "$2="$2, "$3="$3}'
打印結果:Line No:1, No of fields:3 $0=line1 f2 f3 $1=line1 $2=f2 $3=f3 Line No:2, No of fields:3 $0=line2 f4 f5 $1=line2 $2=f4 $3=f5 Line No:3, No of fields:3 $0=line3 f6 f7 $1=line3 $2=f6 $3=f7
使用print $NF可以打印出一行中的最後一個字段,使用$(NF-1)則是打印倒數第二個字段,其他以此類推:
echo -e "line1 f2 f3\n line2 f4 f5" | awk '{print $NF}'打印結果:
f3 f5
echo -e "line1 f2 f3\n line2 f4 f5" | awk '{print $(NF-1)}'
打印結果:
f2 f4
打印每一行的第二和第三個字段:
awk '{ print $2,$3 }' filename
統計文件中的行數:
awk 'END{ print NR }' filename
以上命令只使用了END語句塊,在讀入每一行的時,awk會將NR更新為對應的行號,當到達最後一行NR的值就是最後一行的行號,所以END語句塊中的NR就是文件的行數。
借助-v選項,可以將外部值(並非來自stdin)傳遞給awk:
VAR=10000 echo | awk -v VARIABLE=$VAR '{ print VARIABLE }'
另一種傳遞外部變量方法:
var1="aaa" var2="bbb" echo | awk '{ print v1,v2 }' v1=$var1 v2=$var2
當輸入來自於文件時使用:
awk '{ print v1,v2 }' v1=$var1 v2=$var2 filename
以上方法中,變量之間用空格分隔作為awk的命令行參數跟隨在BEGIN、{}和END語句塊之後。
讀取下一條記錄
awk中next語句使用:在循環逐行匹配,如果遇到next,就會跳過當前行,直接忽略下面語句。而進行下一行匹配。net語句一般用於多行合並:
cat text.txt a b c d e
awk 'NR%2==1{next}{print NR,$0;}' text.txt 2 b 4 d
簡單地讀取一條記錄
awk getline用法:輸出重定向需用到getline函數。getline從標准輸入、管道或者當前正在處理的文件之外的其他輸入文件獲得輸入。它負責從輸入獲得下一行的內容,並給NF,NR和FNR等內建變量賦值。如果得到一條記錄,getline函數返回1,如果到達文件的末尾就返回0,如果出現錯誤,例如打開文件失敗,就返回-1。
當其左右無重定向符|或<時:getline作用於當前文件,讀入當前文件的第一行給其後跟的變量var或$0(無變量),應該注意到,由於awk在處理getline之前已經讀入了一行,所以getline得到的返回結果是隔行的。當其左右有重定向符|或<時:getline則作用於定向輸入文件,由於該文件是剛打開,並沒有被awk讀入一行,只是getline讀入,那麼getline返回的是該文件的第一行,而不是隔行。示例:
執行linux的date命令,並通過管道輸出給getline,然後再把輸出賦值給自定義變量out,並打印它:
awk 'BEGIN{ "date" | getline out; print out }'
執行shell的date命令,並通過管道輸出給getline,然後getline從管道中讀取並將輸入賦值給out,split函數把變量out轉化成數組mon,然後打印數組mon的第二個元素:
awk 'BEGIN{ "date" | getline out; split(out,mon); print mon[2] }'
命令ls的輸出傳遞給geline作為輸入,循環使getline從ls的輸出中讀取一行,並把它打印到屏幕。這裡沒有輸入文件,因為BEGIN塊在打開輸入文件前執行,所以可以忽略輸入文件。
awk 'BEGIN{ while( "ls" | getline) print }'
關閉文件
awk中允許在程序中關閉一個輸入或輸出文件,方法是使用awk的close語句。
close("filename")
filename可以是getline打開的文件,也可以是stdin,包含文件名的變量或者getline使用的確切命令。或一個輸出文件,可以是stdout,包含文件名的變量或使用管道的確切命令。
輸出到一個文件
awk中允許用如下方式將結果輸出到一個文件:
echo | awk '{printf("hello word!\n") > "datafile"}'
或
echo | awk '{printf("hello word!\n") >> "datafile"}'
設置字段定界符
默認的字段定界符是空格,可以使用-F "定界符" 明確指定一個定界符:
awk -F: '{ print $NF }' /etc/passwd
或
awk 'BEGIN{ FS=":" } { print $NF }' /etc/passwd
在BEGIN語句塊中則可以用OFS=“定界符”設置輸出字段的定界符。
在linux awk的while、do-while和for語句中允許使用break,continue語句來控制流程走向,也允許使用exit這樣的語句來退出。break中斷當前正在執行的循環並跳到循環外執行下一條語句。if 是流程選擇用法。awk中,流程控制語句,語法結構,與c語言類型。有了這些語句,其實很多shell程序都可以交給awk,而且性能是非常快的。下面是各個語句用法。
條件判斷語句
if(表達式) 語句1 else 語句2
格式中語句1可以是多個語句,為了方便判斷和閱讀,最好將多個語句用{}括起來。
awk分枝結構允許嵌套,其格式為:
if(表達式) {語句1} else if(表達式) {語句2} else {語句3}
示例:
awk 'BEGIN{ test=100; if(test>90){ print "very good"; } else if(test>60){ print "good"; } else{ print "no pass"; } }'
每條命令語句後面可以用;分號結尾。
循環語句
while語句:
while(表達式) {語句}
示例:
awk 'BEGIN{ test=100; total=0; while(i<=test){ total+=i; i++; } print total; }'
for循環
for循環有兩種格式:
格式一:
for(變量 in 數組) {語句}
格式二:
for(變量;條件;表達式) {語句}
do循環
do {語句} while(條件)
示例:
awk 'BEGIN{ total=0; i=0; do {total+=i;i++;} while(i<=100) print total; }'
其他語句
break 當 break 語句用於 while 或 for 語句時,導致退出程序循環。continue 當 continue 語句用於 while 或 for 語句時,使程序循環移動到下一個迭代。exit 語句使主輸入循環退出並將控制轉移到END,如果END存在的話。如果沒有定義END規則,或在END中應用exit語句,則終止腳本的執行。
數組是awk的靈魂,處理文本中最不能少的就是它的數組處理。因為數組索引(下標)可以是數字和字符串在awk中數組叫做關聯數組(associative arrays)。awk 中的數組不必提前聲明,也不必聲明大小。數組元素用0或空字符串來初始化,這根據上下文而定。
數組的定義
數字做數組索引(下標):
Array[1]="sun" Array[2]="kai"
字符串做數組索引(下標):
Array["first"]="www" Array["last"]="name" Array["birth"]="1987"
讀取數組的值
{ for(item in array) {print array[item]}; } #輸出的順序是隨機的 { for(i=1;i<=len;i++) {print array[i]}; } #len是數組的長度
數組相關函數
得到數組長度:
awk 'BEGIN{info="it is a test";lens=split(info,tA," ");print length(tA),lens;}'
length返回字符串以及數組長度,split進行分割字符串為數組,也會返回分割得到數組長度。
awk 'BEGIN{info="it is a test";split(info,tA," ");print asort(tA);}'
asort對數組進行排序,返回數組長度。
輸出數組內容(無序,有序輸出):
awk 'BEGIN{info="it is a test";split(info,tA," ");for(k in tA){print k,tA[k];}}'
打印結果:
1 it 2 is 3 a 4 test注意:數組下標是從1開始,與C數組不一樣。
判斷鍵值存在以及刪除鍵值:
awk 'BEGIN{tB["a"]="a1";tB["b"]="b1";if( "c" in tB){print "ok";};for(k in tB){print k,tB[k];}}'
打印結果:
a a1 b b1
#刪除鍵值:
awk 'BEGIN{tB["a"]="a1";tB["b"]="b1";delete tB["a"];for(k in tB){print k,tB[k];}}'
打印結果:
b b1
查找字符串(index使用)
awk 'BEGIN{info="this is a test2010test!";print index(info,"test")?"ok":"no found";}'未找到,返回0
正則表達式匹配查找(match使用)
awk 'BEGIN{info="this is a test2010test!";print match(info,/[0-9]+/)?"ok":"no found";}'
截取字符串(substr使用)
awk 'BEGIN{info="this is a test2010test!";print substr(info,4,10);}'
從第 4個 字符開始,截取10個長度字符串
字符串分割(split使用)
awk 'BEGIN{info="this is a test";split(info,tA," ");print length(tA);for(k in tA){print k,tA[k];}}'
打印結果:
4 4 test 1 this 2 is 3 a分割info,動態創建數組tA,這裡比較有意思,awk for …in循環,是一個無序的循環。 並不是從數組下標1…n ,因此使用時候需要注意。
打開外部文件(close用法)
awk 'BEGIN{while("cat /etc/passwd"|getline){print $0;};close("/etc/passwd");}'
逐行讀取外部文件(getline使用方法)
awk 'BEGIN{while(getline < "/etc/passwd"){print $0;};close("/etc/passwd");}'
awk 'BEGIN{print "Enter your name:";getline name;print name;}'
調用外部應用程序(system使用方法)
awk 'BEGIN{b=system("ls -al");print b;}'
下面使用Linux中的Awk對tomcat中日志文件做一些分析,主要統計pv,uv等。
日志文名稱:access_2013_05_30.log,大小57.7 MB 。
這次分析只是簡單演示,所以不是太精確地處理數據。
日志地址:http://download.csdn.net/detail/u011204847/9496357
日志數據示例:
日志總行數:
打印的第七列數據為日志的URL:
擴展,
shell中的管道|
command 1 | command 2 #他的功能是把第一個命令command 1執行的結果作為command 2的輸入傳給command 2
wc -l #統計行數
uniq -c #在輸出行前面加上每行在輸入文件中出現的次數
uniq -u #僅顯示不重復的行
sort -nr
-n:依照數值的大小排序-r:以相反的順序來排序-k:按照哪一列進行排序
head -3 #取前三名
數據清洗:
1、第一次清洗:去除URL中以/static/開頭的URL
awk '($7 !~ /^\/static\//){print $0}' access_2013_05_30.log > clean_2013_05_30.log
去除前:
去除後:
2、第二次清洗:去除圖片、css和js
awk '($7 !~ /\.jpg|\.png|\.jpeg|\.gif|\.css|\.js/) {print $0}' clean_2013_05_30.log > clean2_201 3_05_30.log
PV
pv是指網頁訪問次數
方法:統計所有數據的總行數
數據清洗:對原始數據中的干擾數據進行過濾
awk 'BEGIN{pv=0}{pv++}END{print "pv:"pv}' clean2_2013_05_30.log > pv_2013_05_30
UV
uv指的是訪問人數,也就是獨立IP數
對ip重復的數據進行去重,然後再統計所有行數
awk '{print $1}' clean2_2013_05_30.log |sort -n |uniq -u |wc -l > uv_2013_05_30
訪問最多的IP(前10名)
對ip重復的數據進行去重的時候還要匯總,取前10名
awk '{print $1}' clean2_2013_05_30.log | sort -n | uniq -c |sort -nr -k 1|head -10 > top10_2013_05_30
訪問前十的URL(可以用來分析網站哪個模塊最受歡迎)
awk '{print $7}' clean2_2013_05_30.log | sort | uniq -c |sort -nr -k 1|head -10 > top10URL_2013_ 05_30
使用Java實現訪問量前十的IP地址。
代碼示例:
import java.io.*; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Created by Chen on 2016/4/13. */ public class RegexTest { public static void main(String[] args) throws IOException { //日志格式:27.19.74.143 - - [30/May/2013:17:38:20 +0800] "GET /static/image/common/faq.gif HTTP/1.1" 200 1127 //需要解析的源文件 File file = new File("D:\\clean2_2013_05_30.log"); //高效字符讀入流 BufferedReader bufr = new BufferedReader(new InputStreamReader(new FileInputStream(file))); String line = null; //建立TreeMap,Key為IP地址,Value為IP出現次數 Mapmap = new TreeMap<>(); while ((line = bufr.readLine()) != null) { String ip = parseIP(line); map.put(ip, map.get(ip) == null ? 1 : map.get(ip) + 1); } Set set = map.keySet(); //創建一個具有匿名比較器的TreeSet集合 :作用是讓存入的元素先按照Key排序,相同則繼續按照Value排序。 //這個TreeSet將用來存儲IP地址和出現次數反轉後的每個元素。 TreeSet > treeSet = new TreeSet<>(new Comparator >() { @Override public int compare(Map.Entry o1, Map.Entry o2) { int result = o1.getKey() - o2.getKey(); if (result == 0) { result = o1.getValue().compareTo(o2.getValue()); } return -result; } }); //把IP地址和出現次數反轉,然後放入上面具有比較器的TreeSet中 for (Iterator it = set.iterator(); it.hasNext(); ) { String ip = it.next(); Integer n = map.get(ip); treeSet.add(new AbstractMap.SimpleEntry(n, ip)); } //遍歷並打印出現次數和IP地址(次數最多的前十個) int count = 0; Iterator > itr = treeSet.iterator(); while (itr.hasNext()) { if (count == 10) { break; } Map.Entry en = itr.next(); int n = en.getKey(); System.out.println(n + "\t\t\t" + en.getValue()); count++; } } //解析IP地址 public static String parseIP(String str) { Pattern compile = Pattern.compile("[0-9]+(\\.[0-9]+){3}"); //解析IP地址 Matcher matcher = compile.matcher(str); String group = ""; if (matcher.find()) { group = matcher.group(); } return group; } }