文本处理三剑客之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 < yTrue if xis less than y.
x <= yTrue if x is less than or equal to y.
x > yTrue if x is greater than y.
x >= yTrue if x is greater than or equal to y.
x == yTrue if x is equal to y.
x != yTrue if x is not equal to y.
x ~ yTrue if the string x matches the regexp denoted by y.
x !~ yTrue if the string x does not match the regexp denoted by y.
subscript in arrayTrue 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'
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Brahming

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值