- awk介紹
- awk變量
- printf命令:實現格式化輸出
- 操作符
- awk pattern
- awk action
- awk數組
- awk函數
- awk腳本
一、awk介紹
- 功能:
報告生成器,模式化文本輸出
同時也是一門語言:模式掃描和處理語言
(一)awk的基本語法概念
- 基本格式:
awk [options] 'program' file…
awk [options] -f program_file file…
-
program通常由:BEGIN語句塊、能夠使用模式匹配的通用語句塊、END語句塊,共3部分組成,通常放置于單引號或雙引號中
- program格式:BEGIN{ action;… } pattern{ action;… } END{ action;… }
- pattern:決定動作語句何時觸發及觸發事件
- action statements:對數據進行處理,放在{}內指明
選項:
-F:指明輸入時用到的字段分隔符,默認以空格作為分隔符
-v var=value:自定義變量-
分割符、域和記錄
- 文件的每一行稱為記錄,每一列稱為字段(域)
- awk執行時,由分隔符分隔的字段(域)稱為域標識,標記為$1,$2..$n,$0為所有域。
- 注意:此處的$與shell中變量$符號含義不同
- 省略action,則默認執行print $0 的操作
(二)awk的工作原理
-
第一步:執行BEGIN{action;… }語句塊中的語句
- BEGIN語句塊在awk開始從輸入流中讀取行之前被執行,這是一個可選的語句塊,比如變量初始化、打印輸出表格的表頭等語句通常可以寫在BEGIN語句塊中
-
第二步:從文件或標準輸入(stdin)讀取一行,然后執行pattern{ action;… }語句塊,它逐行掃描文件,從第一行到最后一行重復這個過程,直到文件全部被讀取完畢。
- pattern語句塊中的通用命令是最重要的部分,也是可選的。如果沒有提供pattern語句塊,則默認執行{ print },即打印每一個讀取到的行,awk讀取的每一行都會執行該語句塊
-
第三步:當讀至輸入流末尾時,執行END{action;…}語句塊
- END語句塊在awk從輸入流中讀取完所有的行之后即被執行,比如打印所有行的分析結果這類信息匯總都是在END語句塊中完成,它也是一個可選語句塊
(三)print語句
-
語法:print item1, item2, ...
- 逗號分隔符
- 輸出的各item可以字符串,也可以是數值;當前記錄的字段、變量或awk的表達式
- 省略item,相當于print $0
示例1-1:
awk '{print "hello, awk"}' //等待輸入,每輸入一條打印一次"hello, awk"
awk '{print}' /etc/passwd //逐條打印/etc/passwd文件
awk '{print "wang"}' /etc/passwd //每讀入/etc/passwd文件一行,打印一次"wang"
awk -F: '{print $1}' /etc/passwd //打印/etc/passwd文件以":"作為分隔符的第1列
awk -F: '{print $0}' /etc/passwd //逐條打印/etc/passwd文件
awk -F: '{print $1,$3}' /etc/passwd //打印以":"作為分隔符的第1列和第3列,以空格作為分隔符輸出
awk -F: '{print $1"\t"$3}' /etc/passwd //打印以":"作為分隔符的第1列和第3列,以制表符作為分隔符輸出
tail -3 /etc/fstab | awk '{print $2,$4}' //打印/etc/fstab文件最后3行以空格作為分隔符的第2列和第4列
二、awk變量
(一)內置變量
- FS:輸入字段分隔符,默認為空白字符,可以在program中引用
awk -v FS=':' '{print $1,FS,$3}' /etc/passwd
,注意print中的逗號表示1個空格- awk變量也可以引用Shell變量,便于腳本編寫
fs=":" ; awk -v FS=$fs '{print $1,FS,$3}' /etc/passwd
- awk變量也可以引用Shell變量,便于腳本編寫
- OFS:輸出字段分隔符,默認為空白字符
awk -v FS=':' -v OFS=':' '{print $1,$3,$7}' /etc/passwd
-
RS:輸入記錄分隔符,指定輸入時的換行符,原換行符仍有效
awk -v RS=':' '{print $1}' /etc/passwd
- 注意到當指定以":"作為記錄分隔符時,輸出每條記錄的第1個字段時,除了第1條記錄外,其他記錄的用戶名信息都丟失了。
- 原因在于文件每一行行尾的換行符,當指定其他符號為記錄分隔符后,其自身的換行功能得到保留,所以每一行行尾的shell信息和下一行的用戶名信息被視作為一條記錄,而記錄的第一個字段為bash信息,用戶名信息沒有打印。
-
ORS:輸出記錄分隔符,輸出時用指定符號代替換行符
awk -v RS=':' -v ORS='###' '{print }' /etc/passwd
- 下圖紅框的兩端內容之間沒有"###"分隔,證明屬于一條記錄,出現的換行證明原文件行尾的換行符功能仍舊保留,再次印證上文所述
-
NF:字段數量
-
awk '{print NF,$0}' /etc/fstab
,打印每一行并在行首標明所在行字段數量(以空格為字段分隔符)
-
awk -F: '{print $(NF-1)}' /etc/passwd
,打印每行倒數第2個字段(以冒號為字段分隔符)
-
-
NR:行號
-
awk '{print NR,$0}' /etc/fstab
,打印每行的行號
-
awk '{print NR,$0}' /etc/fstab /etc/inittab
可以同時打印多個文件,但是行號連續編寫
-
FNR:各文件分別計數,行號
awk '{print FNR,$0}' /etc/fstab /etc/inittab
- FILENAME:當前文件名
awk '{print FILENAME}' /etc/fstab
-
ARGC:命令行參數的個數
awk 'BEGIN {print ARGC}' /etc/fstab /etc/inittab
每個紅框內容被視作一個參數
ARGV:數組,保存的是命令行所給定的各參數
awk 'BEGIN {print ARGV[0]}' /etc/fstab /etc/inittab
awk 'BEGIN {print ARGV[1]}' /etc/fstab /etc/inittab
awk 'BEGIN {print ARGV[2]}' /etc/fstab /etc/inittab
(二)自定義變量
-
自定義變量區分大小寫,有兩種定義方式
- -v 選項定義
- 在program中定義
-
實驗:自定義變量的使用
-
awk -v test='hello, gawk' 'BEGIN{print test}'
,直接使用v選項定義 -
awk 'BEGIN{test="hello, gawk";print test}'
,在program中定義
-
awk -F: '{sex="male";print $1,sex,age;age=18}' /etc/passwd
首行沒有第3個字段,因為打印首行第3個字段后才對其變量進行了賦值,之后每一行打印時變量已經賦值,可以正常顯示
- 也可以將program部分寫入文件中,使用awk的-f選項導入
-
echo '{print script,$1,$2}' > awkscript
awk -F: -f awkscript -v script="awk" /etc/passwd
三、printf命令:實現格式化輸出
-
語法:printf "FORMAT", item1, item2, ...
- 必須指定FORMAT
- 不會自動換行,需要顯式給出換行控制符:\n
- FORMAT中需要分別為后面每個item指定格式符
-
格式符:與item對應
- %c:顯示字符的ASCII碼
- %d, %i:顯示十進制整數
- %e, %E:顯示科學計數法數值
- %f:顯示為浮點數
- %g, %G:以科學計數法或浮點形式顯示數值
- %s:顯示字符串
- %u:無符號整數
- %%:顯示%自身
-
修飾符:
- #[.#]:第一個#控制顯示的寬度,第二個#表示小數點后精度,%3.1f
- -:左對齊(默認右對齊)%-15s
- +:顯示數值的正負符號%+d
-
實驗:printf的使用
- 顯示/etc/passwd文件以冒號為分隔符的第1,3列,并且第1列寬20左對齊,第3列寬10右對齊
awk -F: '{printf "%-20s %10d\n",$1,$3}' /etc/passwd
- 顯示/etc/passwd文件以冒號為分隔符的第1,3列,格式:"Username: 第1列內容 , ID: 第3列內容",其中第1列內容寬15左對齊
awk -F: '{printf "Username: %-15s,ID: %d\n",$1,$3}' /etc/passwd
- 顯示/etc/passwd文件以冒號為分隔符的第1,3列,并且第1列寬20左對齊,第3列寬10右對齊
四、操作符
算術操作符:
x+y, x-y, x*y, x/y, x^y, x%y
-x: 轉換為負數
+x: 轉換為數值賦值操作符:
=, +=, -=, *=, /=, %=, ^=
++, --比較操作符:
==, !=,>, >=, <, <=模式匹配符:
~:左邊是否匹配右邊
!~:左邊是否不匹配右邊
示例:
awk -F: '$0 ~ /root/{print $1}' /etc/passwd
打印/etc/passwd文件包含"root"內容行的第一個字段(以":"分隔)
awk '$0 ~ "^root" ' /etc/passwd
打印/etc/passwd文件行首為"root"的行
awk '$0 !~ /root/' /etc/passwd
打印/etc/passwd文件不包含"root"內容的所有行
awk -F: '$3==0' /etc/passwd
打印/etc/passwd文件以":"分隔的第三個字符等于0的所有行邏輯操作符:與&&,或||,非!
示例:
awk -F: '$3>=0 && $3<1000 {print $1}' /etc/passwd
打印root用戶和系統用戶名稱(CentOS 7)
awk -F: '$3==0 || $3>=1000 {print $1}' /etc/passwd
打印root用戶和普通用戶的名稱(CentOS 7)
awk -F: '!($3==0){print $1}' /etc/passwd
打印除root用戶外的所有用戶名稱
awk -F: '!($3>=500) {print $3}' /etc/passwd
打印root用戶和系統用戶的UID(CentOS 6)函數調用:function_name(argu1, argu2, ...)
條件表達式(三目表達式):
selector?if-true-expression:if-false-expression
示例4-1:
awk -F: '{$3>=1000?usertype="Common User":usertype="Sysadmin or SysUser";printf "%15s: %-s\n",$1,usertype}' /etc/passwd
打印用戶名稱,若是普通用戶則其后輸出"Common User",否則輸出"Sysadmin or SysUser",中間以":"分割。用戶名稱寬20右對齊,其后的類型信息左對齊(CentOS 7)
五、awk pattern
- PATTERN:根據pattern條件,過濾匹配的行,再做處理
- 如果未指定:空模式,匹配每一行
(一)正則表達式:/regular expression/
僅處理模式匹配到的行,需要用"/ /"括起來
(二)關系表達式:結果為“真”才會被處理
真:結果為非0值,非空字符串
假:結果為空字符串或0值
(三)行范圍
/pat1/,/pat2/:支持使用正則表達式描述,不支持直接給出數字格式
(四)BEGIN/END模式
BEGIN{}:僅在開始處理文件中的文本之前執行一次
END{}:僅在文本處理完成之后執行一次
示例5-1:pattern中正則表達式和關系表達式的用法
-
awk '/^UUID/{print $1}' /etc/fstab
打印/etc/fstab文件以UUID開頭的行的第1列
awk '!/^UUID/{print $1}' /etc/fstab
打印/etc/fastab文件不以UUID開頭的行的第1列awk -F: 'i=1;j=1{print i,j}' /etc/passwd
本條命令是awk -F: 'i=1{print $0};j=1{print i,j}' /etc/passwd
的簡化,program第1條語句關系判斷為真(i=1),故打印本行;第2條語句關系判斷為真(j=1),故打印i和j值
awk '!0' /etc/passwd; awk '!1' /etc/passwd
打印全部行;一行也不打印awk -F: '$3>=1000{print $1,$3}' /etc/passwd
打印/etc/passwd文件以":"為分隔符的第3列數值大于等于1000的行的第1和第3列awk -F: '$3<1000{print $1,$3}' /etc/passwd
打印/etc/passwd文件以":"為分隔符的第3列數值小于1000的行的第1和第3列awk -F: '$NF=="/bin/bash"{print $1,$NF}' /etc/passwd
打印/etc/passwd文件以":"為分隔符的最后一列是"/bin/bash"的行的第1列和最后1列awk -F: '$NF~ /bash$/{print $1,$NF}' /etc/passwd
打印/etc/passwd文件以":"為分隔符的最后一列是以"bash"作為行尾的行的第1列和最后1列seq 10 | awk 'i=!i'
讀入第1行時,i值取反為1所以打印本行,讀入第2行時,i值為1取反后為0所以不打印本行,讀入第3行與讀入第1行相似,讀入第4行與讀入第2行相似,以此類推,所以結果為打印奇數行
-
seq 10 | awk '!(i=!i)'
或seq 10 | awk -v i=1 'i=!i'
與上文類似,結果為打印偶數行
示例5-2:pattern中行范圍和BEGIN/END模式的用法
-
awk -F: '/^root\>/,/^nobody\>/{print $1}' /etc/passwd
打印/etc/passwd文件從行首是"root"單詞的行至行首是"nobody"單詞的行中以":"作為分隔符的第1列
awk -F: 'BEGIN{printf "%-8s%s\n---------\n","USER","UID"}/^root\>/,/^sync\>/{printf "%-8s%d\n",$1,$3}END{print "========="}' /etc/passwd
BEGIN和END模式適用于打印表頭和表尾
六、awk action
- 常用的action分類
(1) Expressions:算術,比較表達式等
(2) Control statements:if, while等
(3) Compound statements:組合語句
(4) input statements
(5) output statements:print等
(一)awk控制語句:if-else
功能:對awk取得的整行或某個字段做條件判斷
語法:
if(condition) {statement;…} [else statement]
if(condition1) {statement1} else if(condition2){statement2} else {statement3}示例6-1:
awk -F: '{if($3>=1000)print $1,$3}' /etc/passwd
打印普通用戶名稱和UID(CentOS 7)
awk -F: '{if($NF=="/bin/bash") print $1}' /etc/passwd
打印/etc/passwd文件以":"為分隔符的最后一列是"/bin/bash"的行的第1列
awk '{if(NF>5) print $0}' /etc/fstab
打印/etc/fstab文件以空格為分隔符字段數大于5的行
awk -F: '{if($3>=1000) {printf "Common User: %s\n",$1} else {printf "Sysadmin or SysUser: %s\n",$1}}' /etc/passwd
普通用戶輸出"Common User",其他用戶輸出"Sysadmin or SysUser",中間以":"分割,然后打印用戶名稱(CentOS 7)
df| awk -F% '/^\/dev\/sd/{print $1}'| awk '$NF>=80{print $1,$NF}'
檢查磁盤分區占用率,發現占用率大于等于的分區將分區名稱和占用率打印出來
awk -F: '{if($3>=1000){printf "Common User: %s\n",$1} else if($3==0){printf "Sysadmin: %s\n",$1} else{printf "SysUser: %s\n",$1}}' /etc/passwd
普通用戶輸出"Common User",系統用戶輸出SysUser",root用戶輸出"Sysadmin",中間以":"分割,然后打印用戶名稱(CentOS 7)
(二)awk控制語句:while循環
功能:條件“真”,進入循環;條件“假”,退出循環
語法:while(condition){statement;…}
-
示例6-2:
-
awk '/^[[:space:]]*linux16/{i=1;while(i<=NF) {print $i,length($i); i++}}' /etc/grub2.cfg
找出/etc/grub2.cfg文件以不定數量(包含0個)空格作為行首后接字符串"linux16"的行,以空格為分隔符打印這些行每個字段內容和字段長度
-
awk '/^[[:space:]]*linux16/{i=1;while(i<=NF) {if(length($i)>=10) {print $i,length($i)}; i++}}' /etc/grub2.cfg
找出/etc/grub2.cfg文件以不定數量(包含0個)空格作為行首后接字符串"linux16"的行,以空格為分隔符找出長度大于10的字段,把字段和字段長度打印出來
-
(三)awk控制語句:do-while循環
功能:無論真假,至少執行一次循環體
語法:do {statement;…}while(condition)
示例6-3:
awk 'BEGIN{ total=0;i=0;do{ total+=i;i++}while(i<=100);print total}
從1到100求和
(四)awk控制語句:for循環
語法:for(expr1;expr2;expr3) {statement;…}
常見用法:
for(variable assignment;condition;iterationprocess) {for-body}特殊用法:能夠遍歷數組中的元素
語法:for(var in array) {for-body}示例6-4:
awk '/^[[:space:]]*linux16/{for(i=1;i<=NF;i++) {print $i,length($i)}}' /etc/grub2.cfg
找出/etc/grub2.cfg文件以不定數量(包含0個)空格作為行首后接字符串"linux16"的行,以空格為分隔符打印這些行每個字段內容和字段長度
- 實驗:awk, shell腳本和bc的性能比較
time awk 'BEGIN{sum=0;for(i=1;i<=1000000;i++){sum+=i};print sum}'
time (sum=0;for i in {1..1000000};do let sum+=i;done;echo $sum)
time (sum=0;for ((i=1;i<=1000000;i++));do let sum+=i;done;echo $sum)
time seq -s "+" 1000000 | bc
可以看出awk執行效率最高,bc次之,shell腳本的效率最差
(五)awk控制語句:switch, break, continue, next語句
switch語句
語法:switch(expression) {case VALUE1 or /REGEXP/: statement1; case VALUE2 or /REGEXP2/: statement2; ...; default: statementn}break和continue
awk 'BEGIN{sum=0;for(i=1;i<=100;i++) {if(i%2==0)continue;sum+=i}print sum}'
從1至100的奇數求和
awk 'BEGIN{sum=0;for(i=1;i<=100;i++) {if(i==66)break;sum+=i}print sum}'
從1至66求和next
提前結束對本行處理而直接進入下一行處理(awk自身循環)
awk -F: '{if($3%2!=0) next; print $1,$3}' /etc/passwd
輸出UID為偶數的用戶名稱和UID
七、awk數組
(一)awk數組的基本用法
awk數組均為關聯數組:array[index-expression]
index-expression格式:
(1) 可使用任意字符串;字符串要使用雙引號括起來
(2) 如果某數組元素事先不存在,在引用時,awk會自動創建此元素,并將其值初始化為“空串”若要判斷數組中是否存在某元素,要使用“index in array”格式進行遍歷
-
示例7-1:
-
awk '{arr[$0]++;print $0,arr[$0]}' password
輸出文件中的每一行,并且輸出這是第幾次重復本行信息
-
awk '!arr[$0]++' password
刪除重復行,可以對比上圖發現前8行中的重復行已經消失。
解釋:當行信息第一次賦值給數組元素時,數組元素值為空,所以取反為真,輸出此行;而當相同信息第二、三......次賦值時,數組元素值大于等于1,取反為假,不輸出此行,從而實現不輸出重復行。
-
(二)awk數組的遍歷
若要遍歷數組中的每個元素,要使用for循環
for(var in array) {for-body}
var會遍歷array的每個索引示例7-2:
awk 'BEGIN{weekdays["mon"]="Monday";weekdays["tue"]="Tuesday";for (inx in weekdays) {print weekdays[inx]}}'
遍歷輸出weekday數組的值-
實驗:
(1)統計處于不同網絡狀態的tcp連接數分析:首先用
netstat -tan
命令顯示tcp連接情況。統計連接數需要考慮問題:第一,如何過濾有效行,可以在awk中的pattern中設置;第二,怎樣存儲狀態名稱和累加計數,可以利用awk的關聯數組功能,將狀態名稱作為數組下標,并且數組自加1作為計數器;第三,如何輸出每個狀態和狀態的數量,可以利用for循環的遍歷數組功能實現。代碼實現如下:
netstat -tan | awk '/^tcp\>/{ip[$NF]++}END{for (idx in ip) {print idx,ip[idx]}}'
(2)統計access_log文件每個ip地址的記錄行數,并且輸出行數最多的5個ip地址
分析:查看文件格式,發現ip地址位于行首,并且以空格為分隔符正是第1個字段
仿照實驗(1)的思路,將$1作為數組下標實現統計不同ip的記錄數,最后for循環遍歷輸出每個ip地址和記錄行數。之后用sort排序工具按數字倒敘排序,用head工具輸出前五行數據
代碼實現如下:
awk '{ip[$1]++}END{for(idx in ip){print idx,ip[idx]}}' access_log | sort -nr -k2 | head -5
八、awk函數
(一)數值處理:
- rand():返回0和1之間一個隨機數
awk 'BEGIN{srand();for(i=1;i<=10;i++)print int(rand()*100)}'
輸出10個100以內的隨機數,注意使用rand函數前先使用srand函數建立種子
(二)字符串處理:
length([s]):返回指定字符串的長度
sub(r,s,[t]):對t字符串進行搜索r表示的模式匹配的內容,并將第一個匹配的內容替換為s
echo "2008:08:08 08:08:08" | awk 'sub(/:/,"-",$1)'
將以空格為分隔符的第1個字段搜索到的第1個":"替換為"-"
輸出結果為"2008-08:08 08:08:08"gsub(r,s,[t]):對t字符串進行搜索r表示的模式匹配的內容,并全部替換為s所表示的內容
echo "2008:08:08 08:08:08" | awk 'gsub(/:/,"-",$1)'
將以空格為分隔符的第1個字段搜索到的每個":"替換為"-"
輸出結果為"2008-08-08 08:08:08"split(s,array,[r]):以r為分隔符,切割字符串s,并將切割后的結果保存至array所表示的數組中,第一個索引值為1,第二個索引值為2,…
netstat -tan | awk '/^tcp\>/{split($5,ip,":");count[ip[1]]++}END{for (idx in count){print idx,count[idx]}}'
將netstat -tan
輸出結果中的Foreign Address列ip地址(不含端口號)和其出現次數統計出來,只能使用一次awk命令
分析:awk實現了以空格為分隔符取Foreign Address列字段$5的功能,再使用split函數再次將字段以":"為分隔符分割。split函數分割的結果存儲在數組ip中,而需要之后遍歷的信息在數組ip的第一個元素ip[1]中,故將ip[1]再賦值給新的數組count,此后就可以遍歷新數組count統計出現次數
(三)自定義函數:
格式:
function name ( parameter, parameter, ... ) {
statements
return expression
}示例8-1:編寫一個函數,實現輸出兩個變量的較大值
將函數的實現寫在max.awx文件中,執行時導入文件
文件中代碼如下:
function max(var1,var2) {
var1>var2?maxnum=var1:maxnum=var2
return maxnum
}
BEGIN {printf " max num is %d\n",max(a,b)}
執行命令,awx -f max.awx -v a=5 -v b=3
,結果如下
(四)awk調用shell命令:system()
注意:空格是awk中的字符串連接符,如果system中需要使用awk中的變量可以使用空格分隔,或者說除了awk的變量外其他一律用""引用起來。
-
示例8-2:
awk 'BEGIN{system("hostname") }'
相當于在shell中執行hostname命令awk 'BEGIN{score=100; system("echo your score is " score) }'
調用shell命令echo your scre is
,然后輸出awk的變量score的值
九、awk腳本
- 可以將awk程序寫成腳本,直接調用或執行
注意:當執行awk腳本時,注意需要行首添加
#! /bin/awk -f
示例9-1:編寫腳本user.awk,實現分行輸出普通用戶的用戶名和UID(CentOS 7)
代碼如下:
運行腳本前給腳本添加執行權限,執行命令#! /bin/awk -f {if($3>=1000)print $1,$3}
./user.awk -F: /etc/passwd
-
向腳本傳遞參數
格式:
awk_script_file var1=value1 var2=value2... Inputfile注意:在BEGIN過程中不可用。直到首行輸入完成以后,變量才可用。可以通過-v 參數,讓awk在執行BEGIN之前得到變量的值。命令行中每一個指定的變量都需要一個-v參數
示例9-2:編寫腳本uid.awk,輸出指定范圍UID號的用戶名和其UID
代碼如下:
#! /bin/awk -f {if ($3>=min && $3<=max)print $1,$3}
運行腳本前給腳本添加執行權限,執行命令
./uid.awk -F: min=10 max=50 /etc/passwd