一、前言
(一)、awk簡介
awk是一種編程語言,用於在linux/unix下對文本和數據進行處理,數據可以來自標准輸入、一個或多個文件,或其它命令的輸出,它支持用戶自定義函數和動態正則表達式等先進功能,是linux/unix下的一個強大編程工具,它在命令行中使用,但更多的是作為腳本來使用;
awk 的處理文本和數據的方式是這樣的,它逐行掃描文件,從第一行到最后一行,尋找匹配的尋找匹配的特定模式的行,並在這些行上進行你想要的操作,如果沒有指定處理動作,則把匹配的行顯示到標准輸出(屏幕),如果沒有指定模式,則所有被操作所指定的行都被處理;
awk 分別代表其作者是三個人,分別是Alfred Aho、Brian Kernighan、Peter Weinberger。gawk是awk的GNU版本,它提供了Bell實驗和GNU的一些擴展。
(二)、awk語法格式
awk [options] 'commands' filenames awk [options] -f awk-script-file filenames
①、options:
-F 定義輸入字段分隔符,默認的分隔符是空格或制表符(tab)
②、command:
BEGIN{} {} END{}
行處理前 行處理 行處理后
# awk 'BEGIN{print 1/2} {print "ok"} END{print "-----------"}' /etc/hosts 0.5 ok ok ok -----------
BEGIN{} 通常用於定義一些變量,例如BEGIN{FS=":";OFS="---"}
③、awk命令格式
awk 'pattern' filename //示例:awk -F: '/root/' /etc/passwd
awk '{action}' filename //示例:awk -F: '{print $1}' /etc/passwd
awk 'pattern {action}' filename //示例:awk -F: '/root/{print $1,$3}' /etc/passwd //示例:awk 'BEGIN{FS=":"} /root/{print $1,$3}' /etc/passwd
command |awk 'pattern {action}' //示例:df -P| grep '/' |awk '$4 > 25000 {print $4}'
(三)、工作原理
# awk -F: '{print $1,$3}' /etc/passwd
①、awk 使用一行作為輸入,並將這一行賦給這內部變量$0,每行也可以成為一個記錄,以換行符結束;
②、然后,行被:(默認為空格或制表符)分解成字段(或域),每個字段存儲在一編號的變量中,從$1開始,最多達100個字段;
③、awk 如何知道用空格來分割字段的呢?因為有一個內部變量FS來確定字段分割符。初始時,FS賦為空格;
④、awk 打印字段時,將以設置的方法使用print函數打印,awk 在打印字段間加上空格,因為$1,$3之間有一個逗號,逗號比較特殊,它映射為另一個內部變量,稱為輸出字段分割符OFS,OFS默認為空格;
⑤、awk 輸出之后,將從文件中獲取另一行,並將其存儲在$0中,覆蓋原來的內容,然后將新的字符將新的字符串分隔成字段並進行處理,該過成將持續到所有行處理完畢;
(四)、記錄與字段相關內部變量:man awk
$0: //awk變量$0保存當前記錄的內容 # awk -F: '{print $0}' /etc/passwd
NR: The total number of input records seen so far. # awk -F: '{print NR, $0}' /etc/passwd /etc/hosts FNR: The input record number in the current input file # awk -F: '{print FNR, $0}' /etc/passwd /etc/hosts NF: //保存記錄的字段數,$1,$2...$100 # awk -F: '{print $0,NF}' /etc/passwd
FS: //輸入字段分隔符,默認空格 # awk -F: '/alice/{print $1, $3}' /etc/passwd
# awk -F'[ :\t]' '{print $1,$2,$3}' /etc/passwd # awk 'BEGIN{FS=":"} {print $1,$3}' /etc/passwd OFS: //輸出字段分隔符 # awk -F: '/alice/{print $1,$2,$3,$4}' /etc/passwd
# awk 'BEGIN{FS=":"; OFS="+++"} /^root/{print $1,$2,$3,$4}' passwd RS The input record separator, by default a newline. # awk -F: 'BEGIN{RS=" "} {print $0}' a.txt ORS The output record separator, by default a newline. # awk -F: 'BEGIN{ORS=""} {print $0}' passwd
①、區別:
字段分割符:FS OFS 默認空格或制表符
記錄分割符:RS ORS 默認換行符
lab1:
[root@linux ~]# awk 'BEGIN{ORS=" "} {print $0}' /etc/passwd //#將文件每一行合並為一行
ORS默認輸出一條記錄應該回車,加了一個空格
lab2:
[root@linux ~]# head -1 /etc/passwd > passwd1 [root@linux ~]# cat passwd1 root:x:0:0:root:/root:/bin/bash [root@linux ~]# [root@linux ~]# awk 'BEGIN{RS=":"} {print $0}' passwd1 root x 0
0 root /root /bin/bash [root@linux ~]# awk 'BEGIN{RS=":"} {print $0}' passwd1 |grep -v '^$' > passwd2
(五)、格式化輸出:
①、print函數
# date |awk '{print "Month: " $2 "\nYear: " $NF}' # awk -F: '{print "username is: " $1 "\t uid is: " $3}' /etc/passwd # awk -F: '{print "\tusername and uid: " $1,$3 "!"}' /etc/passwd
②、printf函數
# awk -F: '{printf "%-15s %-10s %-15s\n", $1,$2,$3}' /etc/passwd # awk -F: '{printf "|%-15s| %-10s| %-15s|\n", $1,$2,$3}' /etc/passwd
%s 字符類型 %d 數值類型 %f 浮點類型 占15字符 - 表示左對齊,默認是右對齊 printf默認不會在行尾自動換行,加\n
(六)、awk 工作模式和動作
任何awk 語句都由模式和動作組成,模式部分決定動作語句何時觸發及觸發事件,處理即對數據進行的操作。
如果省略模式部分,動作將時刻保持執行狀態,模式可以是任何條件語句或復合語句或正則表達式,模式包括兩個特殊字段 BEGIN和END ,使用 BEGIN語句設置計數和打印頭,BEGIN 語句使用在任何文本瀏覽動作之前,之后文本瀏覽動作依據輸入文本開始執行,END 語句用來在awk 完成文本瀏覽動作后打印輸出文本總數和結尾狀態;
①、正則表達式:
匹配記錄(整行):
# awk '/^alice/' /etc/passwd # awk '$0 ~ /^alice/' /etc/passwd # awk '!/alice/' passwd # awk '$0 !~ /^alice/' /etc/passwd
匹配字段:匹配操作符(~ !~)
# awk -F: '$1 ~ /^alice/' /etc/passwd # awk -F: '$NF !~ /bash$/' /etc/passwd
②、比較表達式:
比較表達采用對文本進行比較,只當條件為真,才執行指定的動作,比較表達式使用關系運算符,用於比較數字與字符串;
關系運算符
運算符 含義 示例 < 小於 x<y <= 小於或等於 x<=y == 等於 x==y != 不等於 x!=y >= 大於等於 x>=y > 大於 x>y
# awk -F: '$3 == 0' /etc/passwd # awk -F: '$3 < 10' /etc/passwd # awk -F: '$NF == "/bin/bash"' /etc/passwd # awk -F: '$1 == "alice"' /etc/passwd # awk -F: '$1 ~ /alic/ ' /etc/passwd # awk -F: '$1 !~ /alic/ ' /etc/passwd # df -P | grep '/' |awk '$4 > 25000'
條件表達式:
# awk -F: '$3>300 {print $0}' /etc/passwd # awk -F: '{ if($3>300) print $0 }' /etc/passwd # awk -F: '{ if($3>300) {print $0} }' /etc/passwd # awk -F: '{ if($3>300) {print $3} else{print $1} }' /etc/passwd
算術運算:+ - * / %(模) ^(冪2^3)
可以在模式中執行計算,awk 都將按浮點方式執行算術運算
# awk -F: '$3 * 10 > 500' /etc/passwd # awk -F: '{ if($3*10>500){print $0} }' /etc/passwd
邏輯操作符和復合模式:
&& 邏輯與 a&&b || 邏輯或 a||b ! 邏輯非 !a # awk -F: '$1~/root/ && $3<=15' /etc/passwd # awk -F: '$1~/root/ || $3<=15' /etc/passwd # awk -F: '!($1~/root/ || $3<=15)' /etc/passwd
范圍模式:
# awk '/Tom/,/Suzanne/' filename
二、示例
(一)、awk 示例:
# awk '/west/' datafile # awk '/^north/' datafile # awk '$3 ~ /^north/' datafile # awk '/^(no|so)/' datafile # awk '{print $3,$2}' datafile # awk '{print $3 $2}' datafile # awk '{print $0}' datafile # awk '{print "Number of fields: "NF}' datafile # awk '/northeast/{print $3,$2}' datafile # awk '/E/' datafile # awk '/^[ns]/{print $1}' datafile # awk '$5 ~ /\.[7-9]+/' datafile # awk '$2 !~ /E/{print $1,$2}' datafile # awk '$3 ~ /^Joel/{print $3 " is a nice boy."}' datafile # awk '$8 ~ /[0-9][0-9]$/{print $8}' datafile # awk '$4 ~ /Chin$/{print "The price is $" $8 "."}' datafile # awk '/Tj/{print $0}' datafile # awk '{print $1}' /etc/passwd # awk -F: '{print $1}' /etc/passwd # awk '{print "Number of fields: "NF}' /etc/passwd # awk -F: '{print "Number of fields: "NF}' /etc/passwd # awk -F"[ :]" '{print NF}' /etc/passwd # awk -F"[ :]+" '{print NF}' /etc/passwd # awk '$7 == 5' datafile # awk '$2 == "CT" {print $1, $2}' datafile # awk '$7 != 5' datafile
(二)、awk 進階:
[root@yang ~]# cat b.txt yang sheng:is a::good boy! [root@yang ~]# awk '{print NF}' b.txt 4 [root@yang ~]# awk -F: '{print NF}' b.txt 4 [root@yang ~]# awk -F"[ :]" '{print NF}' b.txt 7 [root@yang ~]# awk -F"[ :]+" '{print NF}' b.txt 6 # awk '$7 < 5 {print $4, $7}' datafile #{if($7<5){print $4,$7}} # awk '$6 > .9 {print $1,$6}' datafile # awk '$8 <= 17 {print $8}' datafile # awk '$8 >= 17 {print $8}' datafile # awk '$8 > 10 && $8 < 17' datafile # awk '$2 == "NW" || $1 ~ /south/ {print $1, $2}' datafile # awk '!($8 == 13){print $8}' datafile #$8 != 13 # awk '/southem/{print $5 + 10}' datafile # awk '/southem/{print $8 + 10}' datafile # awk '/southem/{print $5 + 10.56}' datafile # awk '/southem/{print $8 - 10}' datafile # awk '/southem/{print $8 / 2 }' datafile # awk '/southem/{print $8 / 3 }' datafile # awk '/southem/{print $8 * 2 }' datafile # awk '/southem/{print $8 % 2 }' datafile # awk '$3 ~ /^Suan/ {print "Percentage: "$6 + .2 " Volume: " $8}' datafile # awk '/^western/,/^eastern/' datafile # awk '{print ($7 > 4 ? "high "$7 : "low "$7)}' datafile //三目運算符 a?b:c 條件?結果1:結果2
# awk '$3 == "Chris" {$3 = "Christian"; print $0}' datafile //賦值運算符
# awk '/Derek/ {$8+=12; print $8}' datafile //$8 += 12等價於$8 = $8 + 12
# awk '{$7%=3; print $7}' datafile //$7 %= 3等價於$7 = $7 % 3
三、awk 腳本編程
(一)、條件判斷
①if 語句:
格式
{if(表達式){語句;語句;...}}
awk -F: '{if($3==0) {print $1 " is administrator."}}' /etc/passwd
awk -F: '{if($3>0 && $3<1000){count++;}} END{print count}' /etc/passwd //統計系統用戶數
②if...else語句:
格式
{if(表達式){語句;語句;...}else{語句;語句;...}}
awk -F: '{if($3==0){print $1} else {print $7}}' /etc/passwd
awk -F: '{if($3==0) {count++} else{i++} }' /etc/passwd
awk -F: '{if($3==0){count++} else{i++}} END{print "管理員個數: "count ; print "系統用戶數: "i}' /etc/passwd
③if...else if...else語句:
格式
{if(表達式1){語句;語句;...} else if (表達式2) {語句;語句;...} else if (表達式3) {語句;語句;...} else {語句;語句;...}}
awk -F: '{if($3==0){i++} else if($3>999){k++} else{j++}} END{print i; print k; print j}' /etc/passwd
awk -F: '{if($3==0){i++} else if($3>999){k++} else{j++}} END{print "管理員個數: "i; print "普通用個數: "k; print "系統用戶: "j}' /etc/passwd
(二)、循環
①、while:
[root@linux ~]# awk 'BEGIN{ i=1; while(i<=10){print i; i++} }' [root@linux ~]# awk -F: '/^root/{i=1; while(i<=7){print $i; i++}}' passwd [root@linux ~]# awk '{i=1; while(i<=NF){print $i; i++}}' /etc/hosts [root@linux ~]# awk -F: '{i=1; while(i<=10) {print $0; i++}}' /etc/passwd //將每行打印10次
[root@linux ~]# cat b.txt 111 222
333 444 555
666 777 888 999 [root@linux ~]# awk '{i=1; while(i<=NF){print $i; i++}}' b.txt //分別打印每行的每列
111
222
333
444
555
666
777
888
999
②、for:
[root@linux ~]# awk 'BEGIN{for(i=1;i<=5;i++){print i} }' //C風格for
1
2
3
4
5 [root@linux ~]# awk -F: '{ for(i=1;i<=10;i++) {print $0} }' /etc/passwd //將每行打印10次
[root@linux ~]# awk -F: '{ for(i=1;i<=NF;i++) {print $i} }' passwd //分別打印每行的每列
root x 0
0 root /root /bin/bash bin x 1
1 bin /bin /sbin/nologin
③、數組:
# awk -F: '{username[++i]=$1} END{print username[1]}' /etc/passwd root # awk -F: '{username[i++]=$1} END{print username[1]}' /etc/passwd bin # awk -F: '{username[i++]=$1} END{print username[0]}' /etc/passwd root
④、數組遍歷:
1.按索引遍歷
# awk -F: '{username[x++]=$1} END{for(i in username) {print i,username[i]} }' /etc/passwd # awk -F: '{username[++x]=$1} END{for(i in username) {print i,username[i]} }' /etc/passwd 注:變量i是索引
2.按元數個數遍歷
# awk -F: '{username[x++]=$1} END{for(i=0;i<x;i++) print i,username[i]}' /etc/passwd # awk -F: '{username[++x]=$1} END{for(i=1;i<=x;i++) print i,username[i]}' /etc/passwd
四、實例練習
(一)、統計shell
<統計/etc/passwd中各種類型shell的數量>
[root@linux ~]# awk -F: '{shells[$NF]++} END{ for(i in shells){print i,shells[i]} }' /etc/passwd
(二)、網站訪問狀態統計
<當前實時狀態netstat>
[root@linux ~]# netstat -ant |grep :80 |awk '{access_stat[$NF]++} END{for(i in access_stat ){print i,access_stat[i]}}' TIME_WAIT 1064 ESTABLISHED 1 LISTEN 1 [root@linux ~]# netstat -ant |grep :80 |awk '{access_stat[$NF]++} END{for(i in access_stat ){print i,access_stat[i]}}' |sort -k2 -n |head [root@linux ~]# ss -an |grep :80 |awk '{access_stat[$2]++} END{for(i in access_stat){print i,access_stat[i]}}' LISTEN 1 ESTAB 5 TIME-WAIT 97 [root@linux ~]# ss -an |grep :80 |awk '{access_stat[$2]++} END{for(i in access_stat){print i,access_stat[i]}}' |sort -k2 -rn TIME-WAIT 18 ESTAB 8 LISTEN 1
(三)、統計當前訪問的每個IP的數量
<當前實時狀態netstat,ss>
[root@linux ~]# netstat -ant |grep :80 |awk -F: '{ip_count[$8]++} END{for(i in ip_count){print i,ip_count[i]} }' |sort
172.16.130.16 289
172.16.130.33 254
172.16.130.44 158
172.16.130.99 4 [root@linux ~]# ss -an |grep :80 |awk -F":" '!/LISTEN/{ip_count[$(NF-1)]++} END{for(i in ip_count){print i,ip_count[i]}}' |sort -k2 -rn |head
172.16.160.77 59
172.16.160.221 16
172.16.160.17 11
172.16.160.69 8
172.16.160.51 7
172.16.160.49 7
172.16.160.13 7
172.16.160.153 3
172.16.160.79 2
172.16.160.52 2
(四)、統計Apache/Nginx日志PV量
<統計Apache/Nginx日志中某一天的PV量,統計日志>
[root@linux log]# grep '22/Mar/2017' cd.mobiletrain.org.log |wc -l 1646
(五)、統計Apache/Nginx日志
<統計Apache/Nginx日志中某一天不同IP的訪問量,日志統計>
[root@linux nginx_log]# grep '07/Aug/2012' access.log |awk '{ips[$1]++} END{for(i in ips){print i,ips[i]} }' |sort -k2 -rn |head
222.130.129.42 5761
123.126.51.94 988
123.126.68.22 588
123.114.46.141 418
61.135.249.218 368
110.75.173.162 330
110.75.173.163 327
110.75.173.161 321
110.75.173.160 319
110.75.173.164 314
[root@linux nginx_log]# grep '07/Aug/2012' access.log |awk '{ips[$1]++} END{for(i in ips){print i,ips[i]} }' |awk '$2>100' |sort -k2 -rn 222.130.129.42 5761
123.126.51.94 988
123.126.68.22 588
123.114.46.141 418
61.135.249.218 368
110.75.173.162 330
110.75.173.163 327
110.75.173.161 321
110.75.173.160 319
110.75.173.164 314
1.202.218.67 313
110.75.173.159 311
203.208.60.80 294
221.221.207.202 266
203.208.60.82 230
203.208.60.81 209
38.111.147.83 206
61.135.249.220 187
183.39.187.86 178
61.156.142.207 129
[root@linux log]# awk '/22\/Mar\/2017/{ips[$1]++} END{for(i in ips){print i,ips[i]}}' sz.mobiletrain.org.log |awk '$2>100' |sort -k2 -rn|head180.153.93.44 1327
119.147.33.19 551
119.147.33.26 234
119.147.33.22 216
119.147.33.21 214
101.69.121.35 209
183.214.128.174 193
175.6.26.173 178
27.221.28.174 167
121.29.54.11 161
[root@linux log]# awk '/22\/Mar\/2017/{ips[$1]++} END{for(i in ips){if(ips[i]>100){print i,ips[i]}}}' sz.mobiletrain.org.log|sort -k2 -rn|head
180.153.93.44 1327
119.147.33.19 551
119.147.33.26 234
119.147.33.22 216
119.147.33.21 214
101.69.121.35 209
183.214.128.174 193
175.6.26.173 178
27.221.28.174 167
121.29.54.11 161
思路:將需要統計的內容(某一個字段)作為數組的索引++
(六)、awk 函數
統計用戶名為4個字符的用戶:
[root@linux ~]# awk -F: '$1~/^....$/{count++; print $1} END{print "count is: " count}' /etc/passwd root sync halt mail news uucp nscd vcsa pcap sshd dbus jack count is: 12
[root@linux ~]# awk -F: 'length($1)==4{count++; print $1} END{print "count is: "count}' /etc/passwd root sync halt mail news uucp nscd vcsa pcap sshd dbus jack count is: 12
(七)、awk 使用外部變量:
①方法一:在雙引號的情況下使用
[root@linux ~]# var="bash" [root@linux ~]# echo "unix script" |awk "gsub(/unix/,\"$var\")" bash script
②方法二:在單引號的情況下使用
[root@linux ~]# var="bash" [root@linux ~]# echo "unix script" |awk 'gsub(/unix/,"'"$var"'")' bash script
[root@linux ~]# df -h Filesystem Size Used Avail Use% Mounted on /dev/mapper/cl-root 2.8T 246G 2.5T 9% / tmpfs 24G 20K 24G 1% /dev/shm /dev/sda2 1014M 194M 821M 20% /boot
[root@linux ~]# df -h |awk '{ if(int($5)>5){print $6":"$5} }'
/:9%
/boot:20%
[root@linux ~]# i=10 [root@linux ~]# df -h |awk '{ if(int($5)>'''$i'''){print $6":"$5} }'
/boot:20%
方法:awk 參數-v(建議)
[root@linux ~]# echo "unix script" |awk -v var="bash" 'gsub(/unix/,var)' bash script [root@linux ~]# awk -v user=root -F: '$1 == user' /etc/passwd root:x:0:0:root:/root:/bin/bash
常用方法:
head 默認為前十;
cat a.txt | awk 'BEGIN{FS="/"}{dict[$3]++}END{for (i in dict)print dict[i],i}' | sort -r | head -20