Linux作爲開源軟件的重要代表,文本的處理對其有着非凡的意義,在介紹了VIM、SED後,本文將Linux另一重要的文本處理工具——awk,包括awk命令的使用,以及awk腳本編寫,以及常用的awk腳本總結
文章目錄
一、概述
由於Linux上衆多文件以文本形式保存,文本處理幾乎是最常見的操作,而其中最知名的工具當屬被稱爲Linux文本處理三劍客的Vim、Sed與AWK了,關於前二者,此前已有介紹:
- Vim:Bash&正則表達式&Vim
- Sed:Linux系統管理-Sed
awk(讀音:[ɔːk])得名與其三個作者Alfred Aho,Peter Weinberger和Brian Kernighan姓氏的首字母,屬于貝爾實驗室的成果,awk工具自有一套語法,遂亦是一門腳本語言,通常我們將其用作數據提取和報告工具
作爲編程語言,其支持變量、數組、函數,可進行選擇、循環等流程控制
AWK後更名爲nawk,而在Linux上使用的爲GNU實現的gawk,若非特別說明,以下的awk均指gawk
二、AWK的執行過程
GAWK(1)中對於該工具的描述爲“pattern scanning and processing language”,不同於Sed將文本對每一行進行處理,AWK通過迭代將輸入中讀取到的每一行的每一個字段分別進行處理,並且將切割的每一段依次以$1
、$2
、$3
、……編號(與Shell編程中的$1
、$2
、$3
、……不同),而$0
則表示所有字段,即當前處理的行
三、基礎用法
awk的基礎使用格式爲
awk [OPTIONS] 'PROGRAM' FILE ...
OPTIONS
-F 指定字段分隔符,默認爲空白符
-v 用戶自定義變量,若需要多個,每一個-v後跟一個自定義變量
PROGRAM awk執行腳本,可指定多個,由;分隔,PROGRAM由兩部分組成:
PATTERN{ACTION}
PATTERN 匹配模式,可使用如下方式
/PAT1/,/PAT2/ 地址定界,即從第一次能被PAT1匹配到的行開始,至第一次能被PAT2匹配到的行結束
/PAT/ 能被PAT匹配到的行
!/PAT/ 取反,即不能被PAT匹配到的行
BOOL_EXPRESSION 布爾表達式
BEGIN 即字面值BEGIN,awk腳本運行前執行一次
END 即字面值END,awk腳本運行完成後執行一次
ACTION 執行的操作
print 打印當前行,其後可跟參數,如print $1即打印第一個字段
如,顯示UID大於500的用戶的用戶名
[root@localhost ~]# awk -F: '$3>500{print $1}' /etc/passwd
polkitd
sssd
colord
libstoragemgmt
nfsnobody
chrony
zhangsan
logstash
abc1
abc2
abc3
abc4
abc5
abc6
abc7
abc8
abc9
abc10
tom
gentoo
user2
user3
user1
顯示用戶shell爲bash的用戶
[root@localhost ~]# awk -F: 'BEGIN{print "=====Start====="};$7~/bash$/{print $1};END{print "======END======"}' /etc/passwd
=====Start=====
root
zhangsan
logstash
abc1
abc2
abc3
abc4
abc5
abc6
abc7
abc8
abc9
abc10
tom
user2
user3
user1
======END======
其中$7~/bash$/
將/etc/passwd/
文件每行的低7個字段(即用戶shell)與模式bash$
匹配,下文將做詳細介紹
四、awk進階
1. awk變量
需要特別指出的是,不同於shell編程,awk中的變量引用不需要加$
內建變量
記錄變量
-
FS
: input Field Separator,讀取文件本時,所使用字段分隔符,默認爲空白字符; -
RS
: Record separator,輸入時所使用的行分隔符,默認爲換行符; -
OFS
: Output Filed Separator,輸出時使用的分隔符,默認爲空白字符; -
ORS
:Output Row Separator,輸出文本信息所使用的行分隔符,默認爲換行符;
如,取出/etc/passwd
第一個字段:awk -v FS=: '{print $1}' /etc/passwd
數據變量
-
NR
: theNumber of input Records,awk命令所處理的記錄數- 如果有多個文件,這個數目會把處理的多個文件中行統一計數
-
NF
:Number of Field,當前記錄的field個數;- awk中的變量引用無需使用
$
,故若使用$NF
則表示最後一個Field
- awk中的變量引用無需使用
-
FNR
:與NR
不同的是,FNR
用於記錄正處理的行是當前處理文件的行數 -
ARGV
: 數組,保存命令行本身這個字符串,如awk '{print $0}' a.txt b.txt
在這個命令中,
ARGV[0]
保存awk,ARGV[1]
保存a.txt; -
ARGC
: awk命令的參數的個數; -
FILENAME
: awk命令當前正在處理的文件的名稱; -
ENVIRON
:當前shell環境變量及其值的關聯數組;如:awk 'BEGIN{print ENVIRON["PATH"]}'
用戶自定義變量
gawk允許用戶自定義自己的變量以便在程序代碼中使用,變量名命名規則與大多數編程語言相同,只能使用字母、數字和下劃線,且不能以數字開頭。gawk變量名稱區分字符大小寫
- 使用
-v
定義awk -v num1=20 -v num2=30 'BEGIN{print num1+num2}'
- 在腳本中定義
awk 'BEGIN{num1=20;num2=30;print num1+num2}' awk '{num1=20;num2=30}{print num1+num2}' /etc/issue
變量賦值
-
- 在腳本中賦值變量
- 在gawk中給變量賦值使用賦值語句進行,例如:
awk 'BEGIN{var="variable testing";print var}'
-
- 在命令行中使用賦值變量
- gawk命令也可以在“腳本”外爲變量賦值,並在腳本中進行引用。例如,上述的例子還可以改寫爲:
awk -v var="variable testing" 'BEGIN{print var}'
2. printf
基礎用法
printf
即Print Formatting,即格式化輸出,同print
,其屬於PROGRAM中的ACTION,可實現較print
高級的格式輸出
其使用格式爲:
printf FORMAT, ITEM1, ITEM2, ...
需要說明的是
-
printf
必須要指定FORMAT
-
FORMAT
用於指定後面的每個ITEM
的輸出格式 -
printf
語句不會自動打印換行符\n
FORMAT
格式的指示符都以%開頭,後跟一個字符;如下:
Format | 描述 |
---|---|
%c |
顯示字符的ASCII碼 |
%d , %i |
十進制整數 |
%e , %E |
科學計數法顯示數值 |
%f |
顯示浮點數 |
%g , %G |
以科學計數法的格式或浮點數的格式顯示數值 |
%s |
顯示字符串 |
%u |
無符號整數 |
%% |
顯示%自身 |
另外,在指定格式時,於%
於字符之間可有修飾符,用於限定輸出樣式,形式如下:
[+|-]M[.N]
+ 顯示數值符號
- 左對齊,不指定則爲右對齊
M 顯示寬度,默認爲右對齊
N 小數點後的精度
如:
[root@localhost ~]# awk -F: '{printf "%20s %-s\n",$1,$3}' /etc/passwd
root 0
bin 1
daemon 2
adm 3
lp 4
sync 5
shutdown 6
halt 7
mail 8
operator 11
games 12
ftp 14
nobody 99
avahi-autoipd 170
dbus 81
polkitd 999
abrt 173
sssd 998
colord 997
ntp 38
libstoragemgmt 996
rpc 32
rtkit 172
usbmuxd 113
rpcuser 29
nfsnobody 65534
mysql 27
tss 59
chrony 995
pulse 171
gdm 42
postfix 89
sshd 74
tcpdump 72
zhangsan 1003
apache 48
systemd-network 192
logstash 1007
abc1 1008
abc2 1009
abc3 1010
abc4 1011
abc5 1012
abc6 1013
abc7 1014
abc8 1015
abc9 1016
abc10 1017
named 25
tom 5001
gentoo 5002
user2 5004
user3 5005
user1 5006
[root@localhost ~]# awk -F: '{printf "%-15s %i\n",$1,$3}' /etc/passwd
root 0
bin 1
daemon 2
adm 3
lp 4
sync 5
shutdown 6
halt 7
mail 8
operator 11
games 12
ftp 14
nobody 99
avahi-autoipd 170
dbus 81
polkitd 999
abrt 173
sssd 998
colord 997
ntp 38
libstoragemgmt 996
rpc 32
rtkit 172
usbmuxd 113
rpcuser 29
nfsnobody 65534
mysql 27
tss 59
chrony 995
pulse 171
gdm 42
postfix 89
sshd 74
tcpdump 72
zhangsan 1003
apache 48
systemd-network 192
logstash 1007
abc1 1008
abc2 1009
abc3 1010
abc4 1011
abc5 1012
abc6 1013
abc7 1014
abc8 1015
abc9 1016
abc10 1017
named 25
tom 5001
gentoo 5002
user2 5004
user3 5005
user1 5006
輸出重定向
printf
可實現類似shell中的重定向:
print items > output-file
print items >> output-file
print items | command
- 文件描述符
-
/dev/stdin
:標準輸入 -
/dev/sdtout
: 標準輸出 -
/dev/stderr
: 錯誤輸出 -
/dev/fd/N
: 某特定文件描述符,如/dev/stdin
就相當於/dev/fd/0
如
awk -F: '{printf "%-15s %i\n",$1,$3 > "/dev/stderr" }' /etc/passwd
3. awk中的操作符
awk
中的操作符與C語言類似,此處僅作總結
算術操作符
-x
: 負值+x
: 轉換爲數值;x^y
: 乘方x**y
: 乘方x*y
: 乘法x/y
:除法x+y
:加法x-y
:減法x%y
:取模
字符串操作符
字符串操作符只有一個,而且不用寫出來,用於實現字符串連接
賦值操作符
-
=
-
+=
-
-=
-
*=
-
/=
-
%=
-
^=
-
**=
-
++
-
--
注意: 如果某模式爲=
號,此時使用/=/
可能會有語法錯誤,應以/[=]/
替代
模式匹配
~
:是否匹配!~
:是否不匹配
比較操作符
比較操作 | 描述 |
---|---|
x < y |
True if xis less than y. |
x <= y |
True if x is less than or equal to y. |
x > y |
True if x is greater than y. |
x >= y |
True if x is greater than or equal to y. |
x == y |
True if x is equal to y. |
x != y |
True if x is not equal to y. |
x ~ y |
True if the string x matches the regexp denoted by y. |
x !~ y |
True if the string x does not match the regexp denoted by y. |
subscript in array |
True if the array array has an element with the subscript subscript. |
邏輯關係操作符
&&
:與
||
:或
!
:非
4. 布爾值
awk
中,任何非0值或非空字符串都爲真,反之就爲假
5. 條件表達式
-
selector?if-true-exp:if-false-exp selector /*條件表達式,若其爲真,執行if-true-exp,否則執行if-false-exp
-
if selector; then if-true-exp else if-false-exp fi
如,判斷系統上用戶的UID,若小於1000則顯示“Common User”,否則顯示“Sysadmin or Sysuser”:
awk -F: '{$3>=1000?usertype="Common User":usertype="Sysadmin or Sysuser";printf "%15s:%-s\n",$1,usertype}' /etc/passwd
6. 函數
函數調用
awk中的函數調用形式爲
function_name(para1,para2,…)
常用內置函數
-
split(string, array [, fieldsep [, seps ] ])
-
功能:將
string
表示的字符串以fieldsep
爲分隔符進行分隔,並將分隔後的結果保存至array
爲名的數組中;數組下標爲從1
開始的序列;如:string
爲root:x:0:0:root:/root:/bin/bash
user[1]=root
user[2]=x
- …
user[7]=/bin/bash
-
例
-
netstat -ant | awk '/:80\>/{split($5,clients,":");IP[clients[1]]++}END{for(i in IP){print IP[i],i}}' | sort -rn | head -50
-
netstat -tan | awk '/:80\>/{split($5,clients,":");ip[clients[4]]++}END{for(a in ip) print ip[a],a}' | sort -rn | head -50
-
netstat -tan | awk '/^tcp\>/{split($5,ip,":");count[ip[1]]++}END{for(i in count){print i,count[i]}}'
-
df -lh | awk '!/^File/{split($5,percent,"%");if(percent[1]>=20){print $1}}'
-
-
length([string])
- 功能:返回
string
字符串中字符的個數
-
substr(string, start [, length])
- 功能:取
string
字符串中的子串,從start
開始,取length
個 start
從1開始計數
-
sub(r, s [,t])
- 功能:以
r
表示的模式,查找t
所表示的字符串中,將第一次匹配到內容替換爲s
所表示的內容
-
gsub(r, s [,t])
- 功能:以
r
表示的模式,查找t
所表示的字符串中,將所有匹配到內容替換爲s
所表示的內容
-
system(command)
- 功能:執行系統
command
並將結果返回至awk
命令
-
rand()
- 功能:返回0到1之間的隨機數
- 若不進行額外處理,後續得到的隨機數與第一次的相同
-
systime()
- 功能:取系統當前時間
-
tolower(s)
- 功能:將
s
中的所有字母轉爲小寫
-
toupper(s)
- 功能:將
s
中的所有字母轉爲大寫
自定義函數
自定義函數使用function
關鍵字,格式如下:
function F_NAME([variable])
{
STATEMENTS
}
此外,函數還可以使用return
語句返回值,格式爲return value
7. Pattern
awk的使用格式爲awk 'program' input-file1 input-file2 ...
其中的program
爲:
pattern { action }
pattern { action }
...
此處再次列出常見的模式類型:
-
1、
/Regexp/
: 正則表達式,格式爲/regular expression/
,僅處理能被此處的模式匹配到的行,如-
awk -F: '$NF~/bash$/{print $1,$NF}' /etc/passwd
-
-
2、
expresssion
: 表達式,其值非0
或爲非空字符時滿足條件,如:$1 ~ /foo/ 或 $1 == "Brahming"
,用運算符~
(匹配)和!~
(不匹配),如:-
awk -F: '$3>=1000{print $1,$3}' /etc/passwd
-
awk -F: '$NF~/bash$/{print $1,$NF}' /etc/passwd
-
-
3、
Line Ranges
: 指定的匹配範圍,格式爲pat1,pat2
,不支持直接指定行號,可使用:-
awk -F: '/^root/,/^user/{print $1}' /etc/passwd
-
awk -F: '(NR>=2&&NR<=10){print $1}' /etc/passwd
-
-
4、
BEGIN
,END
:特殊模式,僅在awk命令執行前運行一次或結束前運行一次BEGIN
:僅在開始處理文件中的文本之前執行一次END
:僅在文本處理完成之後,命令結束之前,執行一次
-
5、
Empty
(空模式):匹配任意輸入行
8. 控制語句
if-else
- 語法:
if (condition) {then-body} if (condition) {then-body} else {else-body}
- 例
awk '{if ($3==0) {print $1, "Adminitrator";} else { print $1,"Common User"}}' /etc/passwd awk -F: '{if ($1=="root") print $1, "Admin"; else print $1, "Common User"}' /etc/passwd awk -F: '{if ($1=="root") printf "%-15s: %s\n", $1,"Admin"; else printf "%-15s: %s\n", $1, "Common User"}' /etc/passwd awk -F: -v sum=0 '{if ($3>=500) sum++}END{print sum}' /etc/passwd awk '{if(NF>5) print}' /etc/fstab awk -F: '{if($NF="/bin/bash") print $1}' /etc/passwd df -h | awk -F[%] '/^\/dev/{print $1}' | awk '{if($NF>=20) print $1}'
while
-
語法:
while (condition){statement1; statment2; ...}
-
例:
awk -F: '{i=1;while (i<=3) {print $i;i++}}' /etc/passwd awk -F: '{i=1;while (i<=NF) { if (length($i)>=4) {print $i}; i++ }}' /etc/passwd awk '{i=1;while (i<=NF) {if ($i>=100) print $i; i++}}' hello.txt # hello.txt文件的內容爲一堆隨機數 awk '/^[[:space:]]*linux16/{i=1;while(i<=NF) {if(length($i)>=7) {print $i,length($i)};i++}}' /etc/grub2.cfg
do-while
do-while
至少執行一次循環體,不管條件滿足與否
-
語法:
do{statement1, statement2, ...} while (condition)
-
例:
awk -F: '{i=1;do {print $i;i++}while(i<=3)}' /etc/passwd awk -F: '{i=4;do {print $i;i--}while(i>4)}' /etc/passwd
for
-
語法:
for ( variable assignment; condition; iteration process) { statement1, statement2, ...}
-
例:
awk -F: '{for(i=1;i<=3;i++) print $i}' /etc/passwd awk -F: '{for(i=1;i<=NF;i++) { if (length($i)>=4) {print $i}}}' /etc/passwd awk '/^[[:space:]]*linux16/{for(i=1;i<=NF;i++){print $i,length($i)}}' /etc/grub2.cfg
awk的for
循環還可以用來遍歷數組元素:
-
語法:
for (i in array) {statement1, statement2, ...}
-
例:統計默認shell
[root@localhost ~]# awk -F: '$NF!~/^$/{BASH[$NF]++}END{for(A in BASH){printf "%15s:%i\n",A,BASH[A]}}' /etc/passwd /bin/sync:1 /bin/bash:17 /sbin/nologin:32 /sbin/halt:1 /bin/false:1 /bin/csh:1 /sbin/shutdown:1
switch
- 語法:
switch (expression) { case VALUE1 or /REGEXP/: statement1; case VALUE1 or /REGEXP/: statement2; … ; default: statement1, ...}
break與continue
與其他語言類似,常用於循環或switch
語句中,也可跳出N層嵌套:
break [N]
next
提前結束對本行文本的處理,並接着處理下一行;例如,下面的命令將顯示其ID號爲奇數的用戶:
awk -F: '{if($3%2==0) next;print $1,$3}' /etc/passwd
exit
退出主輸入循環,進入END
,若沒有END
或END
中有exit
語句,則退出腳本
9. 數組
數組的概念此處不再贅述,在awk中,數組的引用也與其他語言類似:
array[index-expression]
- 說明
-
index-expression
可以使用任意字符串
-
- 所有字符串需要使用雙引號
-
-
如果某數據組元素事先不存在,那麼在引用其時,awk會自動創建此元素並初始化爲空串
- 因此,要判斷某數據組中是否存在某元素,需要使用
index in array
的方式
- 因此,要判斷某數據組中是否存在某元素,需要使用
-
要遍歷數組中的每一個元素,可使用for
語句,此處再次列出:
for (var in array) { statement1, ... }
其中,var
用於引用數組下標,而不是元素值
-
array[var]
: 數組中某一元素的值 -
例:
netstat -ant | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}' # 每出現一被/^tcp/模式匹配到的行,數組S[$NF]就加1,NF爲當前匹配到的行的最後一個字段,此處用其值做爲數組S的元素索引; awk '{counts[$1]++}; END {for(url in counts) print counts[url], url}' /var/log/httpd/access_log # 用法與上一個例子相同,用於統計某日誌文件中IP地的訪問量 awk 'BEGIN{weekdays["mon"]="Monday";weekdays["sun"]="sunday";for(i in weekdays) {print weekdays[i]}}' # i變量會遍歷weekdays的每個索引 awk '{for(i=1;i<=NF;i++){count[$i]++}} END{for(i in count){print i,count[i]}}' /etc/fstab # 顯示文件中每個單詞出現的次數
-
刪除數組變量
從關係數組中刪除數組索引需要使用delete
命令。使用格式爲:delete array[index]
五、例
1. 系統連接狀態查看
-
1.查看TCP連接狀態
netstat -nat |awk ‘{print $6}’|sort|uniq -c|sort -rn netstat -n | awk ‘/^tcp/ {++S[$NF]};END {for(a in S) print a, S[a]}’ 或 netstat -n | awk ‘/^tcp/ {++state[$NF]}; END {for(key in state) print key,"\t",state[key]}’ netstat -n | awk ‘/^tcp/ {++arr[$NF]};END {for(k in arr) print k,"t",arr[k]}’ netstat -n |awk ‘/^tcp/ {print $NF}’|sort|uniq -c|sort -rn netstat -ant | awk ‘{print $NF}’ | grep -v ‘[a-z]‘ | sort | uniq -c
-
2.查找請求數請20個IP(常用於查找攻來源):
netstat -anlp|grep 80|grep tcp|awk ‘{print $5}’|awk -F: ‘{print $1}’|sort|uniq -c|sort -nr|head -n20 netstat -ant |awk ‘/:80/{split($5,ip,":");++A[ip[1]]}END{for(i in A) print A[i],i}’ |sort -rn|head -n20
-
3.用tcpdump嗅探80端口的訪問看看誰最高
tcpdump -i eth0 -tnn dst port 80 -c 1000 | awk -F"." ‘{print $1"."$2"."$3"."$4}’ | sort | uniq -c | sort -nr |head -20
-
4.查找較多time_wait連接
netstat -n|grep TIME_WAIT|awk ‘{print $5}’|sort|uniq -c|sort -rn|head -n20
-
5.找查較多的SYN連接
netstat -an | grep SYN | awk ‘{print $5}’ | awk -F: ‘{print $1}’ | sort | uniq -c | sort -nr | more
-
6.根據端口列進程
netstat -ntlp | grep 80 | awk ‘{print $7}’ | cut -d/ -f1
2. 網站日誌分析(Apache):
-
1.獲得訪問前10位的ip地址
cat access.log|awk ‘{print $1}’|sort|uniq -c|sort -nr|head -10 cat access.log|awk ‘{counts[$(11)]+=1}; END {for(url in counts) print counts[url], url}’
-
2.訪問次數最多的文件或頁面,取前20
cat access.log|awk ‘{print $11}’|sort|uniq -c|sort -nr|head -20
-
3.列出傳輸最大的幾個exe文件(分析下載站的時候常用)
cat access.log |awk ‘($7~/.exe/){print $10 " " $1 " " $4 " " $7}’|sort -nr|head -20
-
4.列出輸出大於200000byte(約200kb)的exe文件以及對應文件發生次數
cat access.log |awk ‘($10 > 200000 && $7~/.exe/){print $7}’|sort -n|uniq -c|sort -nr|head -100
-
5.如果日誌最後一列記錄的是頁面文件傳輸時間,則有列出到客戶端最耗時的頁面
cat access.log |awk ‘($7~/.php/){print $NF " " $1 " " $4 " " $7}’|sort -nr|head -100
-
6.列出最最耗時的頁面(超過60秒的)的以及對應頁面發生次數
cat access.log |awk ‘($NF > 60 && $7~/.php/){print $7}’|sort -n|uniq -c|sort -nr|head -100
-
7.列出傳輸時間超過 30 秒的文件
cat access.log |awk ‘($NF > 30){print $7}’|sort -n|uniq -c|sort -nr|head -20
-
8.統計網站流量(G)
cat access.log |awk ‘{sum+=$10} END {print sum/1024/1024/1024}’
-
9.統計404的連接
awk ‘($9 ~/404/)’ access.log | awk ‘{print $9,$7}’ | sort
-
- 統計http status
cat access.log |awk ‘{counts[$(9)]+=1}; END {for(code in counts) print code, counts[code]}' cat access.log |awk '{print $9}'|sort|uniq -c|sort -rn
-
11.蜘蛛分析,查看是哪些蜘蛛在抓取內容
tcpdump -i eth0 -l -s 0 -w - dst port 80 | strings | grep -i user-agent | grep -i -E 'bot|crawler|slurp|spider'