Linux (三劍客之三) awk命令詳解

openbox2008發表於2018-06-22

awk簡介

awk是一個強大的文字分析工具,相對於grep的查詢,sed的編輯,awk在其對資料分析並生成報告時,顯得尤為強大。簡單來說awk就是把檔案逐行的讀入,以空格為預設分隔符將每行切片,切開的部分再進行各種分析處理。

awk的功能是什麼?與sed和grep很相似,awk是一種樣式掃描與處理工具。但其功能卻大大強於sed和grep。awk提供了極其強大的 功能:它幾乎可以完成grep和sed所能完成的全部工作,同時,它還可以可以進行樣式裝入、流控制、數學運算子、程式控制語句甚至於內建的變數和函式。 它具備了一個完整的語言所應具有的幾乎所有精美特性。實際上,awk的確擁有自己的語言:awk程式設計語言,awk的三位建立者已將它正式定義為:樣式 掃描和處理語言。

一、awk命令形式:

awk [-F|-f|-v] 'BEGIN{} / /{command1; command2} END{}' file
1)[-F|-f|-v]       大引數,-F指定分隔符,-f呼叫指令碼,-v定義變數 var=value
2)''          引用程式碼塊
3)BEGIN    初始化程式碼塊,在對每一行進行處理之前,初始化程式碼,主要是引用全域性變數,設定FS分隔符
4)//        匹配程式碼塊,可以是字串或正規表示式
5){}          命令程式碼塊,包含一條或多條命令
6);          多條命令使用分號分隔
7)END      結尾程式碼塊,在對每一行進行處理之後再執行的程式碼塊,主要是進行最終計算或輸出結尾摘要資訊
以上命令格式,可以是完整的,也可以只是部分。


二、awk的呼叫方式 
(1)awk命令列
命令格式:awk [-F  field-separator]  'commands'  input-file(s)

[-F域分隔符]是可選項,欄位分隔符。commands 是真正awk命令,它可以分為兩部分:'pattern {action}',即用正則查詢'/re/{action}',當pattern為真,則執行{action}

 1)其中pattern引數可以是egrep正規表示式中的任何一個,使用語法/re/再加上一些樣式匹配技巧構成。與sed類似,你也可以使 用","分開兩樣式以選擇某個範圍。
 2)action引數總是被大括號包圍,它由一系統awk語句組成,可以有多個語句,各語句之間用";"分隔。
 3)input-file(s) 是待處理的檔案。

在awk中,檔案的每一行中,由域分隔符分開的每一項稱為一個域。通常,在不指名-F域分隔符的情況下,預設的域分隔符是空格。
awk工作流程:讀入有'\n'換行符分割的一條記錄,然後將記錄按指定的域分隔符劃分域,填充域,$0則表示所有域,$1表示第一個域,$n表示第n個域。預設域分隔符是"空白鍵" 或 "[tab]鍵"。

例1:
[root@controller1 ~]# ll /root
-rw-------. 1 root root       268 Jun  7 17:28 admin-openrc
-rw-------. 1 root root      3068 May  4 01:47 anaconda-ks.cfg
-rw-r--r--  1 root root  13267968 Feb 10  2017 cirros-0.3.5-x86_64-disk.img
-rw-r--r--. 1 root root      3088 May  4 01:47 cobbler.ks
-rw-r--r--  1 root root      3088 Jun  9 11:36 cobbler.ks_2018-06-09
-rw-r--r--  1 root root      3088 Jun  9 11:36 cobbler.ks_2018-06-09+11:36:52
-rw-r--r--  1 root root      3088 Jun  9 11:37 cobbler.ks_2018-06-0911:37:11
-rw-r--r--  1 root root      3088 Jun  9 11:37 cobbler.ks_2018-06-09_11:37:41
-rw-------. 1 root root       262 Jun  7 17:28 demo-openrc

[root@controller1 ~]# ll /root|awk '/cobb/{print $9}' 
cobbler.ks
cobbler.ks_2018-06-09
cobbler.ks_2018-06-09+11:36:52
cobbler.ks_2018-06-0911:37:11
cobbler.ks_2018-06-09_11:37:41
先通過正則,查詢所有cobb的行,並將其第9列輸出來。

[root@controller1 ~]# ll /root|awk '{print $9}'|awk '/^c/'
cirros-0.3.5-x86_64-disk.img
cobbler.ks
cobbler.ks_2018-06-09
cobbler.ks_2018-06-09+11:36:52
cobbler.ks_2018-06-0911:37:11
cobbler.ks_2018-06-09_11:37:41
先輸出所有行的第9列,(此時只有一列資料,就是原來的第9列)再通過正則,輸出以c開頭的行。


(2).將所有的awk命令插入一個單獨檔案,然後呼叫:
呼叫格式:awk -f awk-script-file input-file(s)
其中,-f選項載入awk-script-file中的awk指令碼,input-file(s)跟上面的是一樣的。

使用-f選項呼叫awk程式。awk允許將一段awk程式寫入一個文字檔案,然後在awk命令列中用-f選項呼叫並執行這段程式。

(3).利用命令直譯器呼叫awk程式:
將所有的awk命令插入一個檔案,並使awk程式可執行,然後awk命令直譯器作為指令碼的首行,一遍通過鍵入指令碼名稱來呼叫。
相當於shell指令碼首行的:#!/bin/sh 將其換成:#!/bin/awk 並賦予這個文字檔案以執行的許可權。

三、awk內建變數
awk有許多內建變數用來設定環境資訊,這些變數可以被改變,下面給出了最常用的一些變數。
ARGC               命令列引數個數
ARGV               命令列引數排列
ENVIRON            支援佇列中系統環境變數的使用
FILENAME           awk瀏覽的檔名
FNR                瀏覽檔案的記錄數
FS                 設定輸入域分隔符,等價於命令列 -F選項
NF                 瀏覽記錄的域的個數
NR                 已讀的記錄數
OFS                輸出域分隔符
ORS                輸出記錄分隔符
RS                 控制記錄分隔符
\t            製表符
\n            換行符
-F'[:#/]'    同時定義三個分隔符
~            匹配,與==相比不是精確比較
!~            不匹配,不精確比較
==          等於,必須全部相等,精確比較
!=            不等於,精確比較
&&       邏輯與
||              邏輯或
+            匹配時表示1個或1個以上
/[0-9][0-9]+/   兩個或兩個以上數字
/[0-9][0-9]*/   一個或一個以上數字
$0變數是指整條記錄。$1表示當前行的第一個域,$2表示當前行的第二個域,......以此類推。

例1:匹配有mysql或mail的行,並輸出該行
[root@controller1 ~]# awk '/mysql|mail/{print}' /etc/passwd
mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
mysql:x:27:27:MySQL Server:/var/lib/mysql:/sbin/nologin

與該命令相同:awk -F: '$1~/mail|mysql/{print $0}' /etc/passwd  

例2:以":"分割,第1列匹配有mail字串的,輸出該行
[root@controller1 ~]# awk -F: '$1~/mail/{print $0}' /etc/passwd  
mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
與該命令相同:awk -F: '{if($1~/mail/) print $0}' /etc/passwd 


四、awk內建函式
gsub(reg,string,target) 每次常規表示式reg匹配時替換target中的string 
index(search,string) 返回string中search串的位置 
length(string) 求串string中的字元個數 
match(string,reg) 返回常規表示式reg匹配的string中的位置 
printf(format,variable) 格式化輸出,按format提供的格式輸出變數variable。 
split(string,store,delim) 根據分界符delim,分解string為store的陣列元素 
sprintf(format,variable) 返回一個包含基於format的格式化資料,variables是要放到串中的資料 
strftime(format,timestamp) 返回一個基於format的日期或者時間串,timestmp是systime()函式返回的時間 
sub(reg,string,target) 第一次當常規表示式reg匹配,替換target串中的字串 
substr(string,position,len) 返回一個以position開始len個字元的子串 
totower(string) 返回string中對應的小寫字元 
toupper(string) 返回string中對應的大寫字元 
atan(x,y) x的餘切(弧度) 
cos(x) x的餘弦(弧度) 
exp(x) e的x冪 
int(x) x的整數部分 
log(x) x的自然對數值 
rand() 0-1之間的隨機數 
sin(x) x的正弦(弧度) 
sqrt(x) x的平方根 
srand(x) 初始化隨機數發生器。如果忽略x,則使用system() 
system() 返回自1970年1月1日以來經過的時間(按秒計算) 
print 函式的引數可以是變數、數值或者字串。字串必須用雙引號引用,引數用逗號分隔。
printf 基本相似,可以格式化字串

例1:輸出print和printf
[root@controller1 ~]# awk  -F ':'  '{printf("filename:%10s,linenumber:%s,columns:%s,linecontent:%s\n",FILENAME,NR,NF,$0)}' /etc/passwd
filename:/etc/passwd,linenumber:1,columns:7,linecontent:root:x:0:0:root:/root:/bin/bash
filename:/etc/passwd,linenumber:2,columns:7,linecontent:bin:x:1:1:bin:/bin:/sbin/nologin
filename:/etc/passwd,linenumber:3,columns:7,linecontent:daemon:x:2:2:daemon:/sbin:/sbin/nologin
filename:/etc/passwd,linenumber:4,columns:7,linecontent:adm:x:3:4:adm:/var/adm:/sbin/nologin
filename:/etc/passwd,linenumber:5,columns:7,linecontent:lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
filename:/etc/passwd,linenumber:6,columns:7,linecontent:sync:x:5:0:sync:/sbin:/bin/sync
filename:/etc/passwd,linenumber:7,columns:7,linecontent:shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown


五、awk的模式和操作
awk指令碼是由模式和操作組成的:
格式:pattern {action} 如$ awk '/root/' test,或$ awk '$3 < 100' test。

兩者是可選的,如果沒有模式,則action應用到全部記錄,如果沒有action,則輸出匹配全部記錄。
預設情況下,每一個輸入行都是一條記錄,但使用者可通過RS變數指定不同的分隔符進行分隔。

(1).模式
模式可以是以下任意一個:
1)正規表示式:使用萬用字元的擴充套件集。
2)關係表示式:可以用下面運算子表中的關係運算子進行操作,可以是字元(3)串或數字的比較,如$2>%1選擇第二個欄位比第一個欄位長的行。
4)模式匹配表示式:用運算子~(匹配)和~!(不匹配)。
5)模式,模式:指定一個行的範圍。該語法不能包括BEGIN和END模式。
6)BEGIN:讓使用者指定在第一條輸入記錄被處理之前所發生的動作,通常可在這裡設定全域性變數。
7)END:讓使用者在最後一條輸入記錄被讀取之後發生的動作。

(2).操作
 操作由一個或多個命令、函式、表示式組成,之間由換行符或分號;隔開,並位於大括號{}內。主要有四部份:
1)變數或陣列賦值
2)輸出命令
3)內建函式
4)控制流命令


六、awk的運算子
= += -= *= /= %= ^= **= 賦值
?: C條件表示式
|| 邏輯或
&& 邏輯與
~ ~! 匹配正規表示式和不匹配正規表示式
< <= > >= != == 系運算子
空格 連線
+ - 加,減
* / & 乘,除與求餘
+ - ! 一元加,減和邏輯非
^ *** 求冪
++ -- 增加或減少,作為字首或字尾
$ 欄位引用
in 陣列成員

七、awk的記錄和域(即列)
(1)關於記錄
   awk把每一個以換行符結束的行稱為一個記錄。
   1)記錄分隔符:預設的輸入和輸出的分隔符都是回車,儲存在內建變數ORS和RS中。
   2)$0變數:它指的是整條記錄。如$ awk '{print $0}' test將輸出test檔案中的所有記錄。
   3)變數NR:一個計數器,每處理完一條記錄,NR的值就增加1。

[root@controller1 ~]# awk '{print NR,$0}' admin-openrc 
1 export OS_PROJECT_DOMAIN_NAME=Default
2 export OS_USER_DOMAIN_NAME=Default
3 export OS_PROJECT_NAME=admin
4 export OS_USERNAME=admin
5 export OS_PASSWORD=ADMIN_PASS
6 export OS_AUTH_URL=http://172.16.100.70:5000/v3


(2)域即列
   1)記錄中每個單詞稱做“域”,預設情況下以空格或tab分隔。awk可跟蹤域的個數,並在內建變數NF中儲存該值。  
[root@controller1 ~]# awk -F ':' '{printf("%10s %20s\n",$1,$7)}' /etc/passwd
      root            /bin/bash
       bin        /sbin/nologin
    daemon        /sbin/nologin
       adm        /sbin/nologin
        lp        /sbin/nologin
      sync            /bin/sync  
[root@controller1 ~]# awk -F ':' '{printf("%-20s %-20s\n",$1,$7)}' /etc/passwd
root                 /bin/bash           
bin                  /sbin/nologin       
daemon               /sbin/nologin       
adm                  /sbin/nologin       
lp                   /sbin/nologin       
sync                 /bin/sync              
   

輸出passwd檔案中,以“:”為分割符,將第1,7列的資料,格式化輸出,注意對齊方式。 
   
(3)域分隔符
   1)內建變數FS儲存輸入域分隔符的值,預設是空格或tab。
   可以通過-F命令列選項修改FS的值。如:awk -F : '{print $1,$5}' test.txt 將列印以冒號為分隔符的第一,第五列的內容。
   2)可以同時使用多個域分隔符,這時應該把分隔符寫成放到方括號中。
   如$awk -F '[:/t]' '{print $1,$3}' test,表示以空格、冒號和tab作為分隔符。
   輸出域的分隔符預設是一個空格,儲存在OFS中。如$ awk -F: '{print $1,$5}' test,$1和$5間的逗號就是OFS的值。

八、awk程式設計
(1)條件表示式 ==   !=   >   >=  
awk -F":" '$1=="mysql"{print $0}' /etc/passwd  
awk -F":" '{if($1=="mysql") print $0}' /etc/passwd         //與上面相同 
awk -F":" '$1!="mysql"{print $0}' /etc/passwd              //不等於
awk -F":" '$3>100{print $0}' /etc/passwd                  //大於
awk -F":" '$3>=100{print $0}' /etc/passwd                  //大於等於
awk -F":" '$3<1{print $3}' /etc/passwd                     //小於
awk -F":" '$3<=1{print $3}' /etc/passwd                    //小於等於


(2)邏輯運算子 && || 
awk -F: '$1~/mail/ && $3>3 {print }' /etc/passwd         //邏輯與,$1匹配mail,並且$3>8
awk -F: '{if($1~/mail/ && $3>3) print }' /etc/passwd
awk -F: '$1~/mail/ || $3>100 {print }' /etc/passwd       //邏輯或
awk -F: '{if($1~/mail/ || $3>100) print }' /etc/passwd 


(3)數值運算
awk -F: '$3 > 100' /etc/passwd    
awk -F: '$3 > 100 || $3 < 5' /etc/passwd  
awk -F: '$3+$4 > 200' /etc/passwd
awk -F: '/mysql|mail/{print $3+10}' /etc/passwd           //第三個欄位加10列印 
awk -F: '/mysql/{print $3-$4}' /etc/passwd                //減法
awk -F: '/mysql/{print $3*$4}' /etc/passwd                //求乘積
awk '/MemFree/{print $2/1024}' /proc/meminfo              //除法
awk '/MemFree/{print int($2/1024)}' /proc/meminfo         //取整


(4)輸出分隔符OFS
[root@controller1 ~]# netstat |awk '$6 ~ /WAIT/ || NR==1 {print NR,$4,$5,$6}' OFS="\t" 
75      controller1:59538       controller:openstack-id CLOSE_WAIT
105     controller1:59534       controller:openstack-id CLOSE_WAIT
333     controller1:epmd        controller1:58014       TIME_WAIT
338     controller1:51142       controller1:mysql       TIME_WAIT
379     controller1:8778        compute3:49788  FIN_WAIT2 
//輸出欄位6匹配WAIT的行,其中輸出每行行號,欄位4,5,6,並使用製表符分割欄位,

 
(5)輸出處理結果到檔案
1)使用重定向進行輸出 
[root@controller1 ~]# netstat |awk '$6 ~ /WAIT/ || NR==1 {print NR,$4,$5,$6}' OFS="\t" >netstat.txt     


(6)格式化輸出
netstat -anp|awk '{printf "%-8s %-8s %-10s\n",$1,$2,$3}' 
printf表示格式輸出
%格式化輸出分隔符
左對齊:%-8長度為8個字元
右對齊:%8長度為8個字元
s表示字串型別

[root@controller1 ~]# netstat -anp|awk '$6=="ESTABLISHED" || NR==1 {printf "%-5s %-6s %-25s %-25s \n",NR,$1,$4,$5}'        
1     Active (servers                  and                       
16    tcp    172.16.100.70:11211       172.16.100.70:47038       
17    tcp    172.16.100.70:41336       172.16.100.70:5672        
18    tcp    172.16.100.70:49814       172.16.100.70:11211       
19    tcp    172.16.100.70:47310       172.16.100.70:11211       
20    tcp    172.16.100.70:41354       172.16.100.70:5672        
21    tcp    172.16.100.70:51328       172.16.100.70:3306        
22    tcp    172.16.100.70:51232       172.16.100.70:3306        
23    tcp    172.16.100.70:43500       172.16.100.70:5672        
24    tcp    172.16.100.70:51202       172.16.100.70:3306        
匹配第6列為ESTABLISHED,並且格式化輸出行號,第1,4,5列的資料


(7)IF語句
[root@controller1 ~]# awk -F: 'BEGIN{A=0;B=0} {if($3>10) {A++; print A,"large"} else {B++; print B,"small"}} END{print "LARGE_total:",A,"\t\t","SMALL_total:",B}' /etc/passwd 
1 small
2 small
...
8 small
9 small
1 large
2 large
...
26 large
27 large
LARGE_total: 27                  SMALL_total: 9


語句模組,三部分都用{}括起來,內部用分號,逗號分開,中間過程部分,內部還要用大括號{}:
1)開始模組:BEGIN{A=0;B=0} 
2)中間過程模組:{if($3>10) {A++; print A,"large"} else {B++; print B,"small"}} 
3)結束模組:END{print "LARGE_total:",A,"\t\t","SMALL_total:",B}

在一條命令中,不一定要這三部分都有,只有中間過程模組就行。

(8)while語句
[root@controller1 ~]# awk -F ':' 'BEGIN {count=0;} {name[count] = $1;count++;} END{for (i = 0; i < NR; i++) print i, name[i]}' /etc/passwd
0 root
1 bin
2 daemon
3 adm
4 lp
...
15 polkitd
用一個陣列暫存,取到的資料


[root@controller1 ~]# awk -F: 'BEGIN{i=1} {while(i<NF) {print i,$i;i++}}' /etc/passwd  
1 root
2 x
3 0
4 0
5 root
6 /root
將第1行記錄中的,每一列都分別輸出來。注意在語句中:大括號,分號,逗號的使用。


九、應用例項
(1)統計當前目錄下所有檔案的大小,使用KB作為單位,int是取整的意思
[root@controller1 ~]# ls -l|awk 'BEGIN{sum=0} !/^d/{sum+=$5} END{print "total size is:",int(sum/1024),"KB"}' 
total size is: 298195 KB


(2)統計netstat -anp 狀態為LISTEN和CONNECT的連線數量分別是多少
[root@controller1 ~]# netstat -anp|awk '$6~/LISTEN|CONNECTED/{sum[$6]++} END{for (i in sum) printf "%-10s %-6s %-3s \n", i," ",sum[i]}'
LISTEN            22  
CONNECTED         124 


(3)統計/etc目錄下不同使用者的普通檔案的總數是多少?
[root@controller1 etc]# ls -l|awk 'NR!=1 && !/^d/{sum[$3]++} END{for (i in sum) printf "%-6s %-5s %-3s \n",i," ",sum[i]}' 
tss          1   
root         122 


(4)統計/etc目錄下不同使用者的普通檔案的大小總size是多少?

ls -l|awk 'NR!=1 && !/^d/{sum[$3]+=$5} END{for (i in sum) printf "%-6s %-5s %-3s %-2s \n",i," ",sum[i]/1024/1024,"MB"}'



十、awk與shell之間變數傳遞
(1)awk引用shell變數
1)"'$var'"
[root@controller1 etc]# var="xiaoming"
[root@controller1 etc]# awk 'BEGIN{print "'$var'"}'
xiaoming
這種寫法外層雙引號,內層單引號,$再加變數名。這種寫法其實際是雙括號變為單括號的常量,傳遞給了awk.

如果var中含空格,為了shell不把空格作為分格符,便應該如下使用:
[root@controller1 etc]# var="i am xiao ming" 
[root@controller1 etc]# awk 'BEGIN{print "'"$var"'"}'
i am xiao ming

2)export變數,使用ENVIRON["var"]形式,獲取環境變數的值(變數多建議使用)
定義一個環境變數
[root@controller1 etc]# var="i am xiao ming"
[root@controller1 etc]# export var

檢視一下環境變數
[root@controller1 etc]# env
...
COBBLER_SERVER=172.16.70.110
MAIL=/var/spool/mail/root
var=i am xiao ming
HISTCONTROL=ignoredups
HOME=/root
LOGNAME=root
...
用awk取一個環境變數
[root@controller1 etc]# awk 'BEGIN{print ENVIRON["var"]}'
i am xiao ming

[root@controller1 etc]# awk 'BEGIN{print ENVIRON["MAIL"]}'
/var/spool/mail/root

3)可以使用awk的-v選項(變數不多,建議使用)
[root@controller1 etc]# awk -v str="$var" 'BEGIN{print str}'
i am xiao ming

[root@controller1 etc]# awk -v str="$HOME" 'BEGIN{print str}'    
/root
這樣便把系統變數var和HOME傳遞給了awk變數str.


(2)shell中使用awk變數,利用eval和awk的print結合,獲取awk中的變數值
[root@controller1 ~]# eval $(awk 'BEGIN{print " var1='xiaoming';var2='xiaohong' "}')
[root@controller1 ~]# echo $var1
xiaoming
[root@controller1 ~]# echo $var2
xiaohong

取得/etc/passwd最後一條記錄值
[root@controller1 ~]# eval $(awk -F: '{printf("var1=%s; var2=%s; var3=%s;",$1,$2,$3)}' /etc/passwd) 
[root@controller1 ~]# echo "$var1  $var2 $var3"
neutron  x 993


十一、awk效能比較
(1)定義一個數列:chars=`seq -s " " 100`
[root@controller1 etc]# chars=`seq -s " " 100`
[root@controller1 etc]# echo $chars
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100

(2)用sell命令統計這個數列的字元長度10000次:${#chars}
[root@controller1 etc]# time (for i in $(seq 10000);do count=${#chars};done;)
real    0m0.273s
user    0m0.269s
sys     0m0.004s

(3)用awk的length函式統計這個數列的字元長度10000次:awk 'BEGIN{str="abc";print length(str)}'
[root@controller1 ~]# awk -v str="$chars" 'BEGIN{print length(str)}'
291
[root@controller1 ~]# time (awk -v str="$chars" 'BEGIN{for(i=0;i<=10000;i++){count=length(str)}}')
real    0m0.006s
user    0m0.004s
sys     0m0.003s
可以發現awk進行字元統計,速度是非常快的

(4)其它方法都比較慢:expr length "${chars}"echo $chars |wc -m 
[root@controller1 ~]# time (for i in $(seq 10000);do count=$(expr length "${chars}");done;)
real    0m22.564s
user    0m5.785s
sys     0m17.594s
[root@controller1 ~]# time (for i in $(seq 10000);do count=`echo $chars |wc -m `;done;)
real    0m29.506s
user    0m10.109s
sys     0m28.918s

參考:https://www.cnblogs.com/chengmo/archive/2013/01/17/2865479.html




相關文章