awk是一種模式掃描和處理語言,在對資料進行分析處理時,是十分強大的工具。
awk [options] 'pattern {action}' file...
awk的工作過程是這樣的:按行讀取輸入(标準輸入或檔案),對于符合模式pattern的行,執行action。當pattern省略時表示比對任何字元串;當action省略時表示執行'{print}';它們不可以同時省略。
每一行輸入,對awk來說都是一條記錄(record),awk使用$0來引用目前記錄:
[root@centos7 ~]# head -1 /etc/passwd | awk '{print $0}'
root:x:0:0:root:/root:/bin/bash
例子中将指令head -1 /etc/passwd作為awk的輸入,awk省略了pattern,action為print $0,意為列印目前記錄。
對于每條記錄,awk使用分隔符将其分割成列,第一列用$1表示,第二列用$2表示...最後一列用$nf表示
選項-f表示指定分隔符
如輸出檔案/etc/passwd第一行第一列(使用者名)和最後一列(登入shell):
[root@centos7 ~]# head -1 /etc/passwd | awk -f: '{print $1,$nf}'
root /bin/bash
當沒有指定分隔符時,使用一到多個blank(空白字元,由空格鍵或tab鍵産生)作為分隔符。輸出的分隔符預設為空格。
如輸出指令ls -l *的結果中,檔案大小和檔案名:
[root@centos7 temp]# ls -l * | awk '{print $5,$nf}'
13 b.txt
58 c.txt
12 d.txt
0 e.txt
0 f.txt
24 test.sh
[root@centos7 temp]#
還可以對任意列進行過濾:
[root@centos7 temp]# ls -l *|awk '$5>20 && $nf ~ /txt$/'
-rw-r--r-- 1 nobody nobody 58 11月 16 16:34 c.txt
其中$5>20表示第五列的值大于20;&&表示邏輯與;$nf ~ /txt$/中,~表示比對,符号//内部是正規表達式。這裡省略了action,整條awk語句表示列印檔案大小大于20位元組并且檔案名以txt結尾的行。
awk用nr表示行号
[root@centos7 temp]# awk '/^root/ || nr==2' /etc/passwd
bin:x:1:1:bin:/bin:/sbin/nologin
[root@centos7 temp]#
例子中||表示邏輯或,語句表示:輸出檔案/etc/passwd中以root開頭的行或者第二行。
在一些情況下,使用awk過濾甚至比使用grep更靈活
如獲得ifconfig的輸出中網卡名及其對應的mtu值
[root@idc-v-71253 ~]# ifconfig|awk '/^\s/{print $1"\t"$nf}'
ens32: 1500
ens33: 1500
lo: 65536
[root@idc-v-71253 ~]#
#這裡的正則表示不以空白字元開頭的行,輸出内容中使用\t進行了格式化。
#這裡的正則表示不以空白字元開頭的行,輸出内容中使用\t進行了格式化。
以上所說的nr、nf等都是awk的内建變量,下面列出部分常用内置變量
$0 目前記錄(這個變量中存放着整個行的内容)
$1~$n 目前記錄的第n個字段,字段間由fs分隔
fs 輸入字段分隔符 預設是空格或tab
nf 目前記錄中的字段個數,就是有多少列
nr 行号,從1開始,如果有多個檔案話,這個值也不斷累加。
fnr 輸入檔案行号
rs 輸入的記錄分隔符, 預設為換行符
ofs 輸出字段分隔符, 預設也是空格
ors 輸出的記錄分隔符,預設為換行符
filename 目前輸入檔案的名字
awk中還可以使用自定義變量,如将網卡名指派給變量a,然後輸出網卡名及其對應的rx bytes的值(注意不同模式比對及其action的寫法):
[root@idc-v-71253 ~]# ifconfig|awk '/^\s/{a=$1}/rx p/{print a,$5}'
ens32: 999477100
ens33: 1663197120
lo: 0
awk中有兩個特殊的pattern:begin和end;它們不會對輸入文本進行比對,begin對應的action部分組合成一個代碼塊,在任何輸入開始之前執行;end對應的action部分組合成一個代碼塊,在所有輸入處理完成之後執行。
#注意類似于c語言的指派及print函數用法
[root@centos7 temp]# ls -l *|awk 'begin{print "size name\n---------"}$5>20{x+=$5;print $5,$nf}end{print "---------\ntotal",x}'
size name
---------
total 82
awk還支援數組,數組的索引都被視為字元串(即關聯數組),可以使用for循環周遊數組元素
如輸出檔案/etc/passwd中各種登入shell及其總數量
#注意數組指派及for循環周遊數組的寫法
[root@centos7 temp]# awk -f ':' '{a[$nf]++}end{for(i in a) print i,a[i]}' /etc/passwd
/bin/sync 1
/bin/bash 2
/sbin/nologin 19
/sbin/halt 1
/sbin/shutdown 1
當然也有if分支語句
#注意大括号是如何界定action塊的
[root@centos7 temp]# netstat -antp|awk '{if($6=="listen"){x++}else{y++}}end{print x,y}'
6 3
pattern之間可以用逗号分隔,表示從比對第一個模式開始直到比對第二個模式
[root@centos7 ~]# awk '/^root/,/^adm/' /etc/passwd
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
還支援三目操作符pattern1 ? pattern2 : pattern3,表示判斷pattern1是否比對,true則比對pattern2,false則比對pattern3,pattern也可以是類似c語言的表達式。
如判斷檔案/etc/passwd中uid大于500的登入shell是否為/bin/bash,是則輸出整行,否則輸出uid為0的行:
#注意為避免混淆對目錄分隔符進行了轉義
[root@centos7 ~]# awk -f: '$3>500?/\/bin\/bash$/:$3==0 {print $0}' /etc/passwd
learner:x:1000:1000::/home/learner:/bin/bash
#三目運算符也可以嵌套,例子略
選項-f file表示從file中讀取awk指令
#列印斐波那契數列前十項
[root@centos7 temp]# cat test.awk
begin{
$1=1
$2=1
ofs=","
for(i=3;i<=10;i++)
{
$i=$(i-2)+$(i-1)
}
}
[root@centos7 temp]# awk -f test.awk
1,1,2,3,5,8,13,21,34,55
選項-f指定列分隔符
#多個字元作為分隔符時
[root@centos7 temp]# echo 1.2,3:4 5|awk -f '[., :]' '{print $2,$nf}'
2 5
#這裡-f後單引号中的内容也是正規表達式
選項-v var=val設定變量
#這裡printf函數用法類似c語言同名函數
[root@centos7 ~]# awk -v n=5 'begin{for(i=0;i<n;i++) printf "%02d\n",i}'
00
01
02
03
04
[root@centos7 ~]#
print等函數還支援使用重定向符>和>>将輸出儲存至檔案
#如按第一列(ip)分類拆分檔案access.log,并儲存至ip.txt檔案中
[root@centos7 temp]# awk '{print > $1".txt"}' access.log
[root@centos7 temp]# ls -l 172.20.71.*
-rw-r--r-- 1 root root 5297 11月 22 21:33 172.20.71.38.txt
-rw-r--r-- 1 root root 1236 11月 22 21:33 172.20.71.39.txt
-rw-r--r-- 1 root root 4533 11月 22 21:33 172.20.71.84.txt
-rw-r--r-- 1 root root 2328 11月 22 21:33 172.20.71.85.txt
内建函數
length()獲得字元串長度
[root@centos7 temp]# awk -f: '{if(length($1)>=16)print}' /etc/passwd
systemd-bus-proxy:x:999:997:systemd bus proxy:/:/sbin/nologin
split()将字元串按分隔符分隔,并儲存至數組
[root@centos7 temp]# head -1 /etc/passwd|awk '{split($0,arr,/:/);for(i=1;i<=length(arr);i++) print arr[i]}'
root
x
/root
/bin/bash
getline從輸入(可以是管道、另一個檔案或目前檔案的下一行)中獲得記錄,指派給變量或重置某些環境變量
#從shell指令date中通過管道獲得目前的小時數
[root@centos7 temp]# awk 'begin{"date"|getline;split($5,arr,/:/);print arr[1]}'
09
#從檔案中擷取,此時會覆寫目前的$0。(注意逐行處理b.txt的同時也在逐行從c.txt中獲得記錄并覆寫$0,當getline先遇到eof時<即c.txt檔案行數較少>将輸出空行)
[root@centos7 temp]# awk '{getline <"c.txt";print $4}' b.txt
"https://segmentfault.com/blog/learnning"
#指派給變量
[root@centos7 temp]# awk '{getline blog <"c.txt";print $0"\n"blog}' b.txt
aasdasdadsad
blog address is "https://segmentfault.com/blog/learnning"
#讀取下一行(也會覆寫目前$0)
[root@centos7 temp]# cat file
anny
100
bob
150
cindy
120
[root@centos7 temp]# awk '{getline;total+=$0}end{print total}' file
370
#此時表示隻對偶數行進行處理
next作用和getline類似,也是讀取下一行并覆寫$0,差別是next執行後,其後的指令不再執行,而是讀取下一行從頭再執行。
#跳過以a-s開頭的行,統計行數,列印最終結果
[root@centos7 temp]# awk '/^[a-s]/{next}{count++}end{print count}' /etc/passwd
2
#又如合并相同列的兩個檔案
[root@centos7 temp]# cat f.txt
學号 分值
00001 80
00002 75
00003 90
[root@centos7 temp]# cat e.txt
姓名 學号
張三 00001
李四 00002
王五 00003
[root@centos7 temp]# awk 'nr==fnr{a[$1]=$2;next}{print $0,a[$2]}' f.txt e.txt
姓名 學号 分值
張三 00001 80
李四 00002 75
王五 00003 90
#這裡當讀第一個檔案時nr==fnr成立,執行a[$1]=$2,然後next忽略後面的。讀取第二個檔案時,nr==fnr不成立,執行後面的列印指令
sub(regex,substr,string)替換字元串string(省略時為$0)中首個出現比對正則regex的子串substr
[root@centos7 temp]# echo 178278 world|awk 'sub(/[0-9]+/,"hello")'
hello world
gsub(regex,substr,string)與sub()類似,但不止替換第一個,而是全局替換
[root@centos7 temp]# head -n5 /etc/passwd|awk '{gsub(/[0-9]+/,"----");print $0}'
root:x:----:----:root:/root:/bin/bash
bin:x:----:----:bin:/bin:/sbin/nologin
daemon:x:----:----:daemon:/sbin:/sbin/nologin
adm:x:----:----:adm:/var/adm:/sbin/nologin
lp:x:----:----:lp:/var/spool/lpd:/sbin/nologin
substr(str,n,m)切割字元串str,從第n個字元開始,切割m個。如果m省略,則到結尾
[root@centos7 temp]# echo "hello,世界!"|awk '{print substr($0,8,1)}'
界
tolower(str)和toupper(str)表示大小寫轉換
[root@centos7 temp]# echo "hello,世界!"|awk '{a=toupper($0);print a}'
hello,世界!
system(cmd)執行shell指令cmd,傳回執行結果,執行成功為0,失敗為非0
#此處if語句判斷和c語言一緻,0為false,非0為true
[root@centos7 temp]# awk 'begin{if(!system("date>/dev/null"))print "success"}'
success
match(str,regex)傳回字元串str中比對正則regex的位置
[root@centos7 temp]# awk 'begin{a=match("abc.f.11.12.1.98",/[0-9]{1,3}\./);print a}'
7
awk作為一個程式設計語言可以處理各種各樣的問題,甚至于編寫應用軟體,但它更常用的地方是指令行下的文本分析,生成報表等,這些場景下awk工作的很好。工作中如經常有文本分析的需求,那麼掌握這個指令的用法将為你節省大量的時間。
作者:vvpale
來源:51cto