天天看點

Linux基礎指令介紹八:文本分析awk

Linux基礎指令介紹八:文本分析awk

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) 

    } 

    print 

[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 

/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 

#又如合并相同列的兩個檔案 

[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}' 

awk作為一個程式設計語言可以處理各種各樣的問題,甚至于編寫應用軟體,但它更常用的地方是指令行下的文本分析,生成報表等,這些場景下awk工作的很好。工作中如經常有文本分析的需求,那麼掌握這個指令的用法将為你節省大量的時間。

作者:vvpale

來源:51cto