文本處理三劍客之AWK

Linux作爲開源軟件的重要代表,文本的處理對其有着非凡的意義,在介紹了VIM、SED後,本文將Linux另一重要的文本處理工具——awk,包括awk命令的使用,以及awk腳本編寫,以及常用的awk腳本總結

一、概述

由於Linux上衆多文件以文本形式保存,文本處理幾乎是最常見的操作,而其中最知名的工具當屬被稱爲Linux文本處理三劍客的Vim、Sed與AWK了,關於前二者,此前已有介紹:

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
  • 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, ...

需要說明的是

  1. printf必須要指定FORMAT

  2. FORMAT用於指定後面的每個ITEM的輸出格式

  3. 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. 條件表達式

  1. selector?if-true-exp:if-false-exp
    	selector		/*條件表達式,若其爲真,執行if-true-exp,否則執行if-false-exp
    
  2. 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開始的序列;如:

    stringroot: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、BEGINEND:特殊模式,僅在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,若沒有ENDEND中有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
    
    1. 統計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'
    
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章