文本三劍客之awk
簡介
awk是一種編程語言,用于在linux/unix下對文本和數據進行處理。數據可以來自標準輸入、一個或多個文件,或其它命令的輸出。它支持用戶自定義函數和動態正則表達式等先進功能,是linux/unix下的一個強大編程工具。它在命令行中使用,但更多是作為腳本來使用。awk的處理文本和數據的方式是這樣的,它逐行掃描文件,從第一行到最后一行,尋找匹配的特定模式的行,并在這些行上進行你想要的操作。如果沒有指定處理動作,則把匹配的行顯示到標準輸出(屏幕),如果沒有指定模式,則所有被操作所指定的行都被處理。awk分別代表其作者姓氏的第一個字母。因為它的作者是三個人,分別是Alfred Aho、Brian Kernighan、Peter Weinberger。gawk是awk的GNU版本,它提供了Bell實驗室和GNU的一些擴展。下面介紹的awk是以GUN的gawk為例的,在linux系統中已把awk鏈接到gawk,所以下面全部以awk進行介紹。
本文將從以下幾個方面來介紹
1、awk的命令格式及選項
1.1 awk的使用格式
(1) 命令行格式
awk [options] var=value ‘[ BEGIN{ action;… } ] pattern{ action;… } [ END{action;… } ]' file …
(2) 調用自定義腳本
awk [options] var=value -f programfile file…
說明了awk命令的格式,先看下面的awk命令
awk -F: -v var=value 'BEGIN{print hello } /root/{printf "%-15s %i\n",$1,$3} END{print byebye}' /etc/passwd
hello # 打印BEGIN中的內容 root 0 # 通過/root/匹配查找到的包含root的用戶 operator 11 # 通過/root/匹配查找到的包含root的用戶 byebye # 打印END中的內容
上面awk說明:
- -F: :指定文件作用行的分隔符
- -v var=value :用戶自己在awk中自己指定的變量
- BEGIN{ } :可選的語句塊,awk開始從輸入流中讀取行之前被執行,比如變量初始化、打印輸出表格的表頭等語句通??梢詫懺贐EGIN語句塊中
- END{ } :可選的語句塊,在awk從輸入流中讀取完所有的行之后即被執行,比如打印所有行的分析結果這類信息匯總都是在END語句塊中完成
- pattern{ action;… } : pattern語句塊中的通用命令是最重要的部分,也是可選的。如果沒有提供pattern語句塊,則默認執行{ print },即打印每一個讀取到的行,awk讀取的每一行都會執行該語句塊
- printf :awk的輸出格式定義,這里的printf與C語言中的用法基本相同,通過選項指定以特定的格式輸出,接下來會詳細說明
- $# ,由分隔符分隔的字段(域)標記$1,$2..$n稱為域標識。以下會進行說明,這里先了解一下就好
awk把每一個以換行符結束的行稱為一個記錄 記錄分隔符:默認的輸入和輸出的分隔符都是回車,保存在內建變量ORS和RS中
相信通過上面的awk命令,已經能夠大致了解awk的用法及格式,下面我們就來詳細的介紹一下
1.2 awk的選項
awk的選項[options]比較簡單:
-F fs : 指定輸入文件折分隔符,fs(field separator)是一個字符串或者是一個正則表達式,如-F:。
-v var=value : 用戶定義一個變量并賦值,在awk當中可用
-f programfile : 從腳本文件中讀取awk命令
以上是awk較為常用的命令選項,還有更多選項這里不再過多介紹
2、awk的模式
awk的模式匹配
awk中的模式與sed差不多,都是需要通過設定的模式來定位到某個位置,或則是匹配到某個參數。
下面介紹一下awk數據處理的模式有哪些
- 正則表達式,格式為/regular expression/
- expresssion: 表達式,其值非0或為非空字符時滿足條件
- 指定的匹配范圍,格式為pat1,pat2
- BEGIN/END:特殊模式,僅在awk命令執行前運行一次或結束前運行一次
- 空模式: 匹配任意輸入行
- (1)/regular expression/:僅處理能夠模式匹配到的行,需要用/ /括起來
- awk '/^UUID/{print $1}' /etc/fstab
- awk '!/^UUID/{print $1}' /etc/fstab
[root@Centos6 ~]#awk '/^UUID/{print $1}' /etc/fstab UUID=ef20e35d-b3b0-4bb0-a7f0-b6da5f9478ea UUID=bad2ae77-157a-4e40-a1c0-8d67af3cc105 UUID=90739410-fd0f-4419-900a-2b981300f2d0 UUID=97172ba1-b115-4e5a-b739-8f2b2b309115
命令將包含UUID的行找到并顯示出來
- (2)expression: 關系表達式,結果為“真”才會被處理
- 真:結果為非0值,非空字符串
- 假:結果為空字符串或0值 如:$1 ~ /root/ 或 $1 == "wang",用運算符(匹配)和!(不匹配)。
- awk '!0{print $1}' /etc/issue
[root@Centos6 ~]#awk '!0{print $1}' /etc/issue CentOS Kernel
上面命令!0條件為真,才會執行{print $1},反之同樣道理0或條件為假則不會執行{action}
- (3)/pat1/,/pat2/ 不支持直接給出數字
- awk -F: '/root>/,/nobody>/{print $1}'/etc/passwd
- awk -F: '(NR>=10&&NR<=20){print NR,$1}'/etc/passwd
[root@Centos6 ~]#awk -F: '/^root\>/,/^halt\>/{printf "%-10s %d\n", $1,$3}' /etc/passwd root 0 bin 1 daemon 2 adm 3 lp 4 sync 5 shutdown 6 halt 7
我們看到awk配合printf將用戶名稱從root到halt的行及對應的ID號打印出來,并且在輸出時比較方便,美觀
- (4)BEGIN/END:執行前運行一次或結束前運行一次
- BEGIN:讓用戶指定在第一條輸入記錄被處理之前所發生的動作,通??稍谶@里設置全局變量。
- END:讓用戶在最后一條輸入記錄被讀取之后發生的動作。 awk -F : 'BEGIN {print "user id"}{printf "%-10s %d\n", $1,$3}END{print "end"}' /etc/passwd
[root@Centos6 ~]#awk -F : 'BEGIN {print "user id"}{printf "%-10s %d\n", $1,$3}END{print "end"}' /etc/passwd user id root 0 bin 1 daemon 2 adm 3 ... 部分省略 ... tcpdump 72 ymd 500 end
這里就不在過多說明
- (5)空模式: 匹配每一行
3 awk中的變量
awk也是一門語言,用戶不僅可以通過選項在awk中自定義變量,其自身包含了許多內置有特定含義的變量,用在awk語句中可以方便我們進行使用 下面對其內置變量進行列舉,并對其進行解釋
變量如下:
3.1 awk內置變量
$# 當前記錄的第#個字段,比如#為1表示第一個字段,#為2表示第二個字段 $0 表示變量當前行的所有文本內容。
- FS :輸入字段分隔符,默認為空白字符
[root@Centos6 ~]#awk -v FS=':' '{print $1,FS,$3}' /etc/passwd root : 0 bin : 1 ... 部分省略 ... nobody : 99 dbus : 81
在定義輸入字符段時,-v FS=':' 等價于 -F:
- OFS:輸出字段分隔符,默認為空白字符
[root@Centos6 ~]#awk -F: -v OFS=':' '{print $1,$3}' /etc/passwd root:0 bin:1 daemon:2 ... 部分省略 ... rpc:32 rtkit:499
- RS:輸入文本信息換行符,原換行符仍有效
# 對輸入的文本以空格作為一個行,進行打印
[root@Centos6 ~]#awk -v RS=' ' '{print }' /etc/issue CentOS release 6.9 (Final) Kernel \r on an \m
- ORS:輸出文本信息換行符,用指定符號代替換行符
對輸入的文本以空格作為一個行,輸出文本的換行符用#號代替
[root@Centos6 ~]#awk -v RS=' ' -v ORS='###' '{print }' /etc/issue
CentOS###release###6.9###(Final)
Kernel###\r###on###an###\m
- NF:字段數量
#每個操作行中的字段數量
[root@Centos6 ~]#awk '{print NF}' /etc/issue 4 5
- NR:行號
# 顯示出每個操作行所對應的行號數
[root@Centos6 ~]#awk '{print NR}' /etc/passwd 1 2 3 4 5 6 7 以下省略 ...
- FNR:對輸入的多個文件分別計行號數
# 對輸入的/etc/issue /etc/passwd文件分別計行號數
[root@Centos6 ~]#awk '{print FNR}' /etc/issue /etc/passwd 1 2 1 2 3 4 5 以下省略 ...
- FILENAME:當前文件名
[root@Centos6 ~]#awk '{print FILENAME}' /etc/issue /etc/issue /etc/issue
文件共有兩行,awk默認對所有的行進行處理,才會出現每行都顯示一次當前的文件名
- ARGC:命令行參數的個數
[root@Centos6 ~]#awk '{print ARGC}' /etc/issue 2 2
顯示同上
- ARGV:數組,保存命令行所給定的各參數,只有ARGV[0],ARGV[1]兩個
[root@Centos6 ~]#awk 'BEGIN{print ARGV[0],ARGV[1]}' /etc/fstab /etc/issue awk /etc/fstab
這個命令中,ARGV[0]保存awk,ARGV[1]保存/etc/fstab
3.2 awk自定義變量
除了awk自帶的內置變量,使用者還可以自己定義變量
自定義變量(區分字符大小寫)
(1) -v var=value
(2) 在program中直接定義
awk -v test='hello user' 'BEGIN{print test}' awk 'BEGIN{test="hello,user";print test}' [root@Centos6 ~]#awk -v test='hello user' 'BEGIN{print test}' hello user
4 awk操作符
awk里面的所支持的操作符,與shall當中的操作符大同小異,這里不過的介紹,只列舉出來常見的操作符,及簡單的示例
操作符 | 描述 |
---|---|
x+y, x-y, x*y, x/y, x^y, x%y,-x:轉換為負數,+x: 轉換為數值 | 算數操作符 |
=, +=, -=, *=, /=, %=, ^=,++, — | 賦值操作符 |
==, !=, >, >=, <, <= | 比較操作符 |
~ , !~ | 模式匹配符 |
&&,|| | 邏輯操作符 |
function_name (para1,para2) | 函數表達式 |
? : | 條件表達式 |
in | 數組成員 |
# 模式匹配后執行action
[root@Centos6 ~]#awk -F: '$0 ~ /root/{print $1,$3}' /etc/passwd root 0 operator 11
# 匹配UID為0的行,將其打印出來
[root@Centos6 ~]#awk -F: '$3==0' /etc/passwd root:x:0:0:root:/root:/bin/bash
# 打印UID>=0,并且UID<=500的用戶名及ID號
[root@Centos6 ~]#awk -F: '$3>=0 && $3<=500 {printf "%-10s %d\n",$1,$3}' /etc/passwd root 0 bin 1 daemon 2 adm 3 ... 部分省略 ... tcpdump 72 ymd 500
5 awk的數據處理
awk的輸出操作就是之前例子當中的{print …},接下來我們詳細的對其進行介紹
下面看一下awk的輸出的主要部分
- 輸出命令
- 數組
- 內置函數
- 控制語句
接下來進行詳細的解釋
(1)輸出命令
awk中提供了兩種輸出print和printf
print的使用格式:
使用格式:
print item1, item2, ...
注意:
- 1、各項目之間使用逗號隔開,而輸出時則以空白字符分隔
- 2、輸出的item可以為字符串或數值、當前記錄的字段(如$1)、變量或awk的表達式;數值會先轉換為字符串,而后再輸出
- 3、print命令后面的item可以省略,此時其功能相當于print $0, 因此,如果想輸出空白行,則需要使用print ""
[root@Centos6 ~]#awk 'BEGIN { print "1\n2\n3" }' 1 2 3 [root@Centos6 ~]#awk -F: '{ print $1, $3 }' /etc/passwd root 0 bin 1 daemon 2 adm 3 部分省略 ...
這里需要說明一下: awk -F '[[:space:]:]' {action} awk中可以指定多個分隔符來進行操作,在有時候使用比較方便
printf的使用格式:
使用格式:(與C語言當中的printf命令基本相同)
printf format, item1, item2, ...
注意:
- 1、其與print命令的最大不同是,printf需要指定(格式)format;
- 2、format用于指定后面的每個item的輸出格式;
- 3、printf語句不會自動打印換行符;\n
format格式的指示符都以%開頭,后跟一個字符:
- 格式如下
%c: 顯示字符的ASCII碼
%d, %i:十進制整數
%e, %E:科學計數法顯示數值
%f: 顯示浮點數
%g, %G: 以科學計數法的格式或浮點數的格式顯示數值
%s: 顯示字符串
%u: 無符號整數
%%: 顯示%自身
修飾符: N: 顯示寬度; -: 左對齊; +:顯示數值符號
其中%d、%f、%s較為常用,當處理一些文本格式比較復雜時候printf,更加簡潔,美觀
上文列舉出的例子
[root@Centos6 ~]#awk -F: '{printf "%-15s %i\n",$1,$3}' /etc/passwd root 0 bin 1 daemon 2 adm 3 ... 部分省略 ... gdm 42 pulse 497 sshd 74 tcpdump 72 ymd 500
看起來很是舒服!
(2)數組
在awk內部,只支持關聯數組
其格式為:
array[index-expression]
注意:
index-expression
- (1) 因其是關聯數組,可以使用任意字符串表示,但要注意不要設定成,awk的內置變量
- (2) 如果數組元素事先不存在,那么在引用其時,awk會自動創建此元素并初始化為空串
因此,要判斷某數據組中是否存在某元素,需要使用index in array的方式 要遍歷數組中的每一個元素,需要使用如下的特殊結構: for (var in array) { statement1, … } 其中,var用于引用數組下標,而不是元素值
# 定義數組并賦值輸出
[root@Centos6 ~]#awk 'BEGIN{test[1]="one";test[2]="two";print test[1]}' one
# 遍歷數組,并對出現相同匹配值計數,如下例子為統計日志中IP的訪問量
[root@Centos6 ~]# awk '{test[$1]++}END{for(i in test){printf "%-18s %-d\n", i,test[i]}}' ./access_log 172.18.253.3 59 172.18.250.59 447 172.18.34.199 162 172.18.50.99 10 172.18.252.197 377 172.18.250.194 3 172.18.99.99 11 172.18.11.1 195 172.18.251.196 363 172.18.251.112 1044 以下省略 ...
test[$1]++ : 以每個記錄的字段為標示,并對出現相同的記錄計數
for(i in test ): 對遍歷數組中的所有元素,并將數組中的標示賦值給i
刪除定義的數組
delete array[index]
(3)內置函數
awk也是較高大上了,當然會用到函數了,下面我們來看一下
內置數學函數:
數學函數如下表:
函數名稱 | 返回值 |
---|---|
rand() | 0-1之間的數 |
sin(x) | 正弦函數 |
cos(x) | 余弦函數 |
atan2(x,y) | 余切函數 |
int(x) | 取整 |
sqrt(x) | 平方根 |
示例
# 隨機生成5個數乘以100,并對其結果取整 [root@Centos6 ~]# awk 'BEGIN{rand(); for (i=1;i<=5;i++)print int(rand()*100) }' 29 84 15 58 19
字符串函數
- 1、length("s"): 指定返回字符串的長度
- 2、sub(r,s,"t"):對t字符串進行搜索,r為匹配的內容,并將第一個匹配到的r用s替換掉
# sub替換第一次匹配的內容
[root@Centos6 ~]#date "+%F"|awk 'sub(/-/,":",$0)' 2017:09-05
# gsub為貪婪模式,替換所有匹配到的內容
[root@Centos6 ~]#date "+%F"|awk 'gsub(/-/,":",$0)' 2017:09:05
3、split(s,array,"r"):以r為分隔符,對s字符串進行切割,并將切割好的字段結果保存到指定的array的數組中,第一個為array[1],第二個為[2],以此類推
[root@Centos6 ~]#date "+%F"|awk 'split($0,test,"-");END{print test[0],test[1],test[2],test[3]}' 2017-09-05 2017 09 05
自定義函數
自定義函數格式:
function name ( parameter, parameter, ... ) { statements return expression }
自定義函數,可以指定輸入的參數個數,以及返回的參數表達式
示例:
#定義一個比較大小的函數
function max(v1,v2) {
v1>v2?var=v1:var=v2 return var } BEGIN{a=3;b=2;print max(a,b)}
(4)、控制語句:
if-else
格式:if (condition) {then-body} else {[ else-body ]}
示例:
[root@Centos6 ~]#awk -F: '{if ($3<500) {printf "%-15s %-s\n", $1, "Admin"} else {printf "%-15s %-s\n", $1, "Common User"}}' /etc/passwd root Admin bin Admin daemon Admin adm Admin lp Admin ... 部分省略 ... ymd Common User 部分省略 ...
注意
單條語句{}花括號可不加,但';'分號要加
while
格式: while (condition){statement1; statment2; …}
[root@Centos6 ~]#awk -F: '{i=1;while (i<=NF) { if (length($i)<=5) {print $i}; i++ }}' /etc/passwd root x 0 0 root /root bin 以下省略 ...
do-while
格式: do {statement1, statement2, …} while (condition)
# 計算1-100的和
[root@Centos6 ~]#awk 'BEGIN{i=1;do{sum+=i;i++}while(i<=100){print sum}}' 5050
for
格式: for ( variable assignment; condition; iteration process) { statement1, statement2, …}
# 1-100的和
[root@Centos6 ~]#awk 'BEGIN{for(i=0;i<=100;i++)sum+=i;print sum}' 5050
for循環還可以用來遍歷數組元素:
格式: for (i in array) {statement1, statement2, …}
# 統計/etc/passwd文件中的bash類型
[root@Centos6 ~]#awk -F: '$NF!~/^$/{BASH[$NF]++}END{for(A in BASH){printf "%15s:%i\n",A,BASH[A]}}' /etc/passwd /sbin/shutdown:1 /bin/bash:3 /sbin/nologin:29 /sbin/halt:1 /bin/sync:1
case
格式: switch (expression) { case VALUE or /REGEXP/: statement1, statement2,… default: statement1, …}
break 和 continue
常用于循環或case語句中,含義及用法與shell中的相同,這里不再贅述
next
提前結束對本行文本的處理,并接著處理下一行; 例如,下面的命令將顯示其ID號為奇數的用戶:
# 統計ID為奇數的用戶
[root@Centos6 ~]#awk -F: '{if($3%2==0) next;printf "%-10s %d\n", $1,$3}' /etc/passwd bin 1 adm 3 sync 5 halt 7 operator 11 gopher 13 nobody 99 dbus 81 usbmuxd 113 rtkit 499 vcsa 69 abrt 173 rpcuser 29 postfix 89 mysql 27 pulse 497
以上就是awk的全部內容,由于本片文章是基于部分操作寫成的,難免有不足及錯誤的地方,敬請諒解!
原創文章,作者:M25_ymd,如若轉載,請注明出處:http://www.www58058.com/86347
謝謝分享