Linux 学习与进阶之三剑客其二 Awk
后知后觉 暂无评论

Awk 是一种优良的文本处理工具,Linux 及 Unix 中现有的功能最强大的数据处理引擎之一。命令提供了极其强大的功能:可以进行正则表达式的匹配,样式装入、流控制、数学运算符、进程控制语句甚至于内置的变量和函数。它具备了一个完整的语言所应具有的几乎所有精美特性。

前言

历史

Awk 其名称得自于它的创始人阿尔佛雷德·艾侯(Alfred Aho)、彼得·温伯格(Peter Weinberger)和布莱恩·柯林汉(Brian Kernighan)姓氏的首个字母,Awk 被创建于 1978 年在诺基亚贝尔实验室。

实际上 Awk 不仅是一种程序,更是一种语言, Awk 是一种数据驱动型脚本语言,包含一系列针对文本数据流的操作,用于提取或转换文本,例如生成格式报表。该语言广泛使用字符串数据类型,关联数组(即由键字符串索引的数组)和正则表达式。 虽然 Awk 的应用领域有限,并且专门用于支持单线程序,但该语言是图灵完备的,甚至 Awk 的早期贝尔实验室用户也经常编写结构良好的大型程序。

gawk 是 Awk 的GNU版本。

最简单地说,Awk是一种用于处理文本的编程语言工具。Awk 在很多方面类似于 Shell 编程语言,尽管Awk具有完全属于其本身的语法。它的设计思想来源于SNOBOL4sedMarc Rochkind设计的有效性语言、语言工具yacclex,当然还从C语言中获取了一些优秀的思想。在最初创造Awk时,其目的是用于文本处理,并且这种语言的基础是,只要在输入数据中有模式匹配,就执行一系列指令。该实用工具扫描文件中的每一行,查找与命令行中所给定内容相匹配的模式。如果发现匹配内容,则进行下一个编程步骤。如果找不到匹配内容,则继续处理下一行。

命令结构

awk [options] 'BEGIN{}{表达式}END{}' file

执行过程

    1.命令行赋值 -v 
    2.执行BEGIN{}里面的内容
    3.读取文件第一行
    4.进行判断(条件)
        1)执行对应的动作
        2)继续读取下一行
        3)文件结尾
    5.最后执行END{}里面的内容 

内置变量

$NR    行号
$NF    最后一列  

参数说明

  1. 参数 -vFS 指定字段分隔符,等价于 -F (常用),不指定时默认为空格。
FS: Field Separator 字段分隔符(列)
  1. 参数 -vRS 指定记录分隔符,不指定时默认为回车\n
RS:Record Separator 记录分隔符(行)
  1. 参数 -vOFS 指定输出分隔符,输出时用输出分隔符来替换默认的字段分隔符,仅在命令行输出内容时显示。
OFS:Output Field Separator 输出分隔符

举个例子

指定字段分隔符

[root@domain ~]# ip a s eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 00:16:3e:cd:c1:9f brd ff:ff:ff:ff:ff:ff
    inet 104.222.62.32/24 brd 104.2??.???.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::216:3eff:fecd:c19f/64 scope link 
       valid_lft forever preferred_lft forever
[root@domain ~]# ip a s eth0 | awk -F "[^0-9.]+"  'NR==3{print $2}'
104.222.62.32

指定记录分隔符

[root@domain ~]# awk '{print NR,$0}' passwd.txt | head -n10
1 root:x:0:0:root:/root:/bin/bash
2 bin:x:1:1:bin:/bin:/sbin/nologin
3 daemon:x:2:2:daemon:/sbin:/sbin/nologin
4 adm:x:3:4:adm:/var/adm:/sbin/nologin
5 lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
6 sync:x:5:0:sync:/sbin:/bin/sync
7 shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
8 halt:x:7:0:halt:/sbin:/sbin/halt
9 mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
10 operator:x:11:0:operator:/root:/sbin/nologin

现在修改记录分隔符(行)来看看效果

[root@domain ~]# awk -vRS=: '{print NR,$0}' passwd.txt | head -n10
1 root
2 x
3 0
4 0
5 root
6 /root
7 /bin/bash
bin
8 x
9 1

可以看出来 awk 命令重新指定:为行结尾

指定输出分隔符

[root@domain ~]# awk  -F ":"  -vOFS="@" '$1=$1{print $0}' passwd.txt | head -n10
root@x@0@0@root@/root@/bin/bash
bin@x@1@1@bin@/bin@/sbin/nologin
daemon@x@2@2@daemon@/sbin@/sbin/nologin
adm@x@3@4@adm@/var/adm@/sbin/nologin
lp@x@4@7@lp@/var/spool/lpd@/sbin/nologin
sync@x@5@0@sync@/sbin@/bin/sync
shutdown@x@6@0@shutdown@/sbin@/sbin/shutdown
halt@x@7@0@halt@/sbin@/sbin/halt
mail@x@8@12@mail@/var/spool/mail@/sbin/nologin
operator@x@11@0@operator@/root@/sbin/nologin

运行模式

正则表达式作为模式

例一

cat >>value.list<<'EOF'
#排名 名称 市值(亿美元) 所在国家 行业
NO. NAME VALUE(BillionDollars) Country Industry
01 Apple 8858.88 USA CONSUMER-ELECTRONICS
02 Google 8251.05 USA INTERNET
03 Microsoft 7257.14 USA INTERNET
04 Amazon 6756.09 USA INTERNET
05 Tencent 5724.75 CHN INTERNET
06 Facebook 5517.97 USA INTERNET
07 BerkshireHathaway 5363.00 USA INSURANCE
08 Alibaba 5256.00 CHN INTERNET
09 JPMorganChase 4035.98 USA BANK
10 ICBC 3958.31 CHN BANK
EOF

问题 1.1:打印 Facebook 的所在行

[root@domain ~]# awk '$2~/Facebook/' value.list
06 Facebook 5517.97 USA INTERNET

问题 1.2:打印 Facebook排名市值

[root@domain ~]# awk '$2~/Facebook/{print $1,$3}' value.list
06 5517.97

问题 1.3:显示以A开头的企业所在行

[root@domain ~]# awk '$2~/^A/' value.list
01 Apple 8858.88 USA CONSUMER ELECTRONICS
04 Amazon 6756.09 USA INTERNET
08 Alibaba 5256.00 CHN INTERNET

问题 1.4:显示以A开头的企业全名市值

[root@domain ~]# awk '$2~/^A/{print $2,$3}' value.list
Apple 8858.88
Amazon 6756.09
Alibaba 5256.00

问题 1.5:输出文件的第二行和第三行

[root@domain ~]# awk 'NR==2' value.list
NO. NAME VALUE(BillionDollars) Country Industry
[root@domain ~]# awk 'NR==3' value.list
01 Apple 8858.88 USA CONSUMER ELECTRONICS
[root@domain ~]# awk 'NR==3{print $0}' value.list
01 Apple 8858.88 USA CONSUMER ELECTRONICS
[root@domain ~]# awk 'NR==3{print}' value.list
01 Apple 8858.88 USA CONSUMER ELECTRONICS
小贴士:此例题可以看出 $0 为整行,且 print 的默认参数就是 $0

问题 1.6:打印每个公司的市值,去除小数点且每个值时都有以B$结尾,如8858B$

[root@domain ~]# awk '{gsub(/\..*/,"B$",$3);print}' value.list | column -t
#排名  名称               市值(亿美元)         所在国家  行业
NO.    NAME               VALUE(BillionDollars)  Country   Industry
01     Apple              8858B$                 USA       CONSUMER   ELECTRONICS
02     Google             8251B$                 USA       INTERNET
03     Microsoft          7257B$                 USA       INTERNET
04     Amazon             6756B$                 USA       INTERNET
05     Tencent            5724B$                 CHN       INTERNET
06     Facebook           5517B$                 USA       INTERNET
07     BerkshireHathaway  5363B$                 USA       INSURANCE
08     Alibaba            5256B$                 CHN       INTERNET
09     JPMorganChase      4035B$                 USA       BANK
10     ICBC               3958B$                 CHN       BANK
小贴士:awk 内置函数 gsub gsub == global substitute 全局替换;column -t 可以使得列对齐(使用空格)美化输出结果。

小结

在 `awk` 中使用正则表达式作为条件,也可以用 `$列~` 的形式用来精准匹配。
`'//'` 寻找字符
`'$3~//'` 在第三列中寻找字符
`~`  匹配
`!~` 不匹配

例二

cat >>donate.txt<<"EOF"
TOM :334:224
CAIN :776:633
JANE :375:200
EOF

问题 2.1:将:全部换成$

[root@domain ~]# awk '{gsub(/:/,"$",$NF);print}' donate.txt 
TOM $334$224
CAIN $776$633
JANE $375$200
小贴士:gsub(/要找的内容/,"替换为什么",列) 全局替换格式。

比较表达式为模式

例三

问题 3.1:打印 value.list 中三行后的内容(排除前方的说明内容)

[root@domain ~]# awk 'NR>=3{print}' value.list 
01 Apple 8858.88 USA CONSUMER ELECTRONICS
02 Google 8251.05 USA INTERNET
03 Microsoft 7257.14 USA INTERNET
04 Amazon 6756.09 USA INTERNET
05 Tencent 5724.75 CHN INTERNET
06 Facebook 5517.97 USA INTERNET
07 BerkshireHathaway 5363.00 USA INSURANCE
08 Alibaba 5256.00 CHN INTERNET
09 JPMorganChase 4035.98 USA BANK
10 ICBC 3958.31 CHN BANK
提示:其他可用比较表达式>>=<<===!=

指定范围模式

例四

cat >nginx.conf<<'EOF'
server
{
    listen       80;
    server_name  domain1.com www.domain1.com;
    access_log   logs/domain1.access.log  main;
    root         html;
}

server
{
    listen       80;
    server_name  domain2.com www.domain2.com;
    access_log   logs/domain2.access.log  main;
}
EOF

问题 4.1:显示 nginx.conf 中从{开始到}结束的行

[root@domain ~]# awk '/{/,/}/' nginx.conf 
{
    listen       80;
    server_name  domain1.com www.domain1.com;
    access_log   logs/domain1.access.log  main;
    root         html;
}
{
    listen       80;
    server_name  domain2.com www.domain2.com;
    access_log   logs/domain2.access.log  main;
}

BEGIN{} / END{} 模式

BEGIN{} 在读取文件内容之前执行里面的内容
一般用于测试(计算)
设置或修改awk内置变量

    awk -F:
    awk -vFS=:
    awk 'BEGIN{FS=":"}'

    awk -vOFS=:
    awk 'BEGIN{OFS=":"}'

END{} 处理完文件内容后执行里面的内容
一般用于显示最终结果

例五

问题 5.1:统计 /etc/serivces 文件中空行的数量

[root@domain ~]# awk '/^$/{i=i+1}END{print i}' /etc/services 
16

问题 5.2:统计 /etc/passwd 中虚拟用户的数量(以 /sbin/nologin 为命令解释器)(不同系统下可能结果不同)

[root@domain ~]# awk -F: '$NF=="/sbin/nologin"' /etc/passwd
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
···
省略
···
[root@domain ~]# awk '/nologin$/{i++}END{print i}' /etc/passwd
33
# 用 grep 验证一下
[root@domain ~]# grep '/sbin/nologin$' /etc/passwd | wc -l
33

例六

问题 6.1:统计某文件的某列的总和

[root@domain ~]# seq 10 | awk '{sum=sum+$1;print sum}'
1
3
6
10
15
21
28
36
45
55

可用看出来每次循环都会计算并输出一次结果。

[root@domain ~]# seq 10 | awk '{sum=sum+$1}END{print sum}'
55

例七

问题 7.1:统计 NGINX 访问日志 access.log 中第10列(页面大小)的总和,可用来粗略统计流量使用情况。

[root@domain ~]# awk '{sum=sum+$10}END{print sum}' access.log
2478496663
[root@domain ~]# awk '{sum=sum+$10}END{print sum/1024^3" GB"}' access.log 
2.30828 GB

附表:例题练习文件点此下载

[root@domain ~]# head -2 access.log 
101.226.61.184 - - [22/Nov/2015:11:02:00 +0800] "GET /mobile/sea-modules/gallery/zepto/1.1.3/zepto.js HTTP/1.1" 200 24662 "http://m.papaonline.com.cn/mobile/theme/ppj/home/index.html" "Mozilla/5.0 (Linux; U; Android 5.1.1; zh-cn; HUAWEI CRR-UL00 Build/HUAWEICRR-UL00) AppleWebKit/533.1 (KHTML, like Gecko)Version/4.0 MQQBrowser/5.4 TBS/025478 Mobile Safari/533.1 MicroMessenger/6.3.7.51_rbb7fa12.660 NetType/3gnet Language/zh_CN"
101.226.61.184 - - [22/Nov/2015:11:02:00 +0800] "GET /mobile/theme/ppj/common/js/baiduAnalytics.js HTTP/1.1" 200 526 "http://m.papaonline.com.cn/mobile/theme/ppj/home/index.html" "Mozilla/5.0 (Linux; U; Android 5.1.1; zh-cn; HUAWEI CRR-UL00 Build/HUAWEICRR-UL00) AppleWebKit/533.1 (KHTML, like Gecko)Version/4.0 MQQBrowser/5.4 TBS/025478 Mobile Safari/533.1 MicroMessenger/6.3.7.51_rbb7fa12.660 NetType/3gnet Language/zh_CN"
#第1列 IP地址
#第4-5列 时间
#第6列 请求方式 
#第7列 访问的页面
#第8列 使用协议
#第9列 请求状态
#第10列 请求页面的大小(默认单位字节)
#第11列 来源页面
#第12-18列 浏览器版本、系统类型、渲染引擎等,统称 UA

数组

awk 中条件语句可以使用 if 判断或 for 循环,和SEHLL脚本中略有不同。

# awk 中条件语句
if () command;
# awk 中循环语句
for () command;
# awk 中的数组
array[element]
数组名称[元素名称]

例八

cat >url.txt<<'EOF'
http://www.etiantian.org/index.html
http://www.etiantian.org/1.html
http://post.etiantian.org/index.html
http://mp3.etiantian.org/index.html
http://www.etiantian.org/3.html
http://post.etiantian.org/2.html
EOF

问题 8.1:统计以上文本中的三级域名的出现次数

使用常规手段

[root@domain ~]# awk -F"[/.]+" '{print $2 }'  url.txt | sort -nr | uniq -ic
      3 www
      2 post
      1 mp3

尝试使用数组进行实现

[root@domain ~]# awk -F "[/.]+" '{h[$2]++;print h["www"]}' url.txt 
1
2
2
2
3
3

可以看出来每次检测到行中有www就会使数组 h[www] 中的数字增加1

[root@domain ~]# awk -F "[/.]+" '{h[$2]++}END{print h["www"],h["post"],h["mp3"]}' url.txt 
3 2 1

即可显示全部的三级域名,但是此种方式存在问题,实际统计时需要把全部情况列出来,使用很不方便。可以使用数组自动存储出现的三级域名,以便进行统计。

[root@domain ~]# awk -F "[/.]+" '{h[$2]++}END{for(pol in h) print pol,h[pol]}' url.txt | column -t
www   3
mp3   1
post  2

使用数组pol存储全部出现过的三级域名,使用h[pol]存储每个三级域名出现的次数,然后打印即可。

例九

问题 9.1:分析系统登录日志,检查是否存在暴力破解行为,并统计每个IP的破解次数。

附表:例题练习文件点此下载

[root@domain ~]# tail -1 secure-20161219
Dec 19 03:42:01 localhost sshd[9030]: Failed password for root from 59.63.166.84 port 65111 ssh2

先查看一下文件,可以看出登录失败的关键字是 Failed ,而筛选的IP是在 fromport之间的。

[root@domain ~]# awk '/Failed/{h[$(NF-3)]++}END{for(pol in h) print pol,h[pol]}' secure-20161219 | sort -rnk2 | head | column -t
218.65.30.25    68652
218.65.30.53    34326
218.87.109.154  21201
112.85.42.103   18065
112.85.42.99    17164
218.87.109.151  17163
218.87.109.150  17163
218.65.30.61    17163
218.65.30.126   17163
218.65.30.124   17163

问题 9.2:分析每个用户被破解的次数

[root@domain ~]# awk '/Failed/{h[$(NF-5)]++}END{for(pol in h) print pol,h[pol]}' secure-20161219 | sort -rnk2 | head | column -t
root      364610
admin     733
user      246
oracle    119
support   104
guest     79
test      70
ubnt      47
pi        41
webadmin  36

例十

问题 10.1:统计 access.log 中,每个页面(url) 所占的总大小并按大小排序。

[root@domain ~]# awk '{size[$7]=size[$7]+$10}END{for (pol in size)print pol,size[pol]}' access.log | sort -nrk2 | head | column -t
/mobile/theme/ppj/home/images/20151111/03.png           115161246
/mobile/theme/ppj/home/images/20151111/04.png           103371446
/mobile/theme/ppj/home/images/20151111/02.png           103021063
/mobile/theme/ppj/home/images/20151111/05.png           82828309
/online/ppjonline/images/product/product_90601.png      66944817
/online/ppjonline/images/product/product_90602.png      66503095
/online/ppjonline/images/product/product_90600.png      65244493
/online/ppjonline/images/ad/20151111/4.png?v=201401020  62519515
/mobile/theme/ppj/home/images/20151111/2.png            62005913
/mobile/theme/ppj/home/images/20151111/01.png           60493565

问题 10.2:统计 access.log 中每个页面出现的次数、总大小、URL名,并按次数进行排序。

[root@domain ~]# awk '{size[$7]=size[$7]+$10;count[$7]++}END{for (pol in size)print count[pol],size[pol],pol}' access.log | sort -k1nr | head | column -t
4838  37176198  /online/api/mc/cart/new/getCart.json
3859  254694    /online/api/mc/sys/nowTime.json
2445  176320    /online/mc/crm/integration/points/pointBalance.json
1872  2061750   /online/api/mc/cart/save.json
1797  86208     /ccbs/global/commonPage/includeHead/contextPath.jsp
1548  895893    /mobile/theme/ppj/account/tpl/footerTpl.html
1344  2402180   /online/api/mc/productCategory/children.json?language=zh_CN&productCategoryCode=ONLINE_SPECIAL_MENU
912   699386    /mobile/theme/ppj/product/tpl/productCategoryTpl.html
838   368120    /ccbs/global/scripts/jquery/plugins/images/loading.gif

问题 10.3:统计 access.log 中每个IP的访问次数,并按照次数进行排序,显示前十位。

[root@domain ~]# awk '{t[$1]++}END{for (pol in t) print pol, t[pol]}' access.log | sort -nrk2 | head | column -t
58.220.223.62   12049
112.64.171.98   10856
114.83.184.139  1982
117.136.66.10   1662
115.29.245.13   1318
223.104.5.197   961
116.216.0.60    957
180.111.48.14   939
223.104.5.202   871
223.104.4.139   869

问题 10.4:统计 access.log 中访问次数最多的图片资源并统计其占用的网络流量。

[root@domain ~]# awk '$7~/bmp|png|jpg|gif/{n[$7]+=1;s[$7]=s[$7]+$10}END{for (i in n) print n[i],s[i],i}' access.log | sort -nr | head| column -t
838  368120     /ccbs/global/scripts/jquery/plugins/images/loading.gif
614  2713447    /mobile/theme/ppj/home/images/placeholder.jpg
540  5783412    /online/ppjonline/images/product_category/product_category_1145.jpg
518  9656178    /mobile/static/common/src/loadingimg.gif
509  82828309   /mobile/theme/ppj/home/images/20151111/05.png
504  14146968   /online/ppjonline/images/product/product_90968.png
498  115161246  /mobile/theme/ppj/home/images/20151111/03.png
497  103021063  /mobile/theme/ppj/home/images/20151111/02.png
493  60493565   /mobile/theme/ppj/home/images/20151111/01.png
489  103371446  /mobile/theme/ppj/home/images/20151111/04.png

问题 10.5:统计 access.log 中每分钟uri请求次数最多的前五个时间点(按请求次数排序)。

[root@domain ~]# awk '{time=substr($4,2,11)" "substr($4,14,5);n[time" "$7]++}END{for(i in n)print i,n[i]}' access.log |sort -rnk4 | head -5 | column -t
22/Nov/2015  11:37  /online/api/mc/cart/new/getCart.json  151
22/Nov/2015  11:29  /online/api/mc/cart/new/getCart.json  117
22/Nov/2015  11:38  /online/api/mc/cart/new/getCart.json  116
22/Nov/2015  11:38  /online/api/mc/sys/nowTime.json       110
22/Nov/2015  11:34  /online/api/mc/cart/new/getCart.json  109

附录

参考链接

本文撰写于一年前,如出现图片失效或有任何问题,请在下方留言。博主看到后将及时修正,谢谢!
禁用 / 当前已拒绝评论,仅可查看「历史评论」。