Linux 学习与进阶之三剑客其二 AWK

in Linux with 0 comment

Awk


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

前言

历史

Awk 这种编程及数据操作语言(其名称得自于它的创始人阿尔佛雷德·艾侯、彼得·温伯格和布莱恩·柯林汉姓氏的首个字母)的最大功能取决于一个人所拥有的知识。
实际上Awk的确拥有自己的语言:Awk程序设计语言,三位创建者已将它正式定义为“样式扫描和处理语言”。它允许创建简短的程序,这些程序读取输入文件、为数据排序、处理数据、对输入执行计算以及生成报表,还有无数其他的功能。gawkAwkGNU版本。

最简单地说,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{}里面的内容 

参数说明

  1. 参数 -vFS 指定字段分隔符,等价于 -F (常用),不指定时默认为空格。

    FS: Field Separator 字段分隔符(列)

  2. 参数 -vRS 指定记录分隔符,不指定时默认为回车\n

    RS:Record Separator 记录分隔符(行)

  3. 参数 -vOFS 指定输出分隔符,输出时用输出分隔符来替换默认的字段分隔符,仅在命令行输出内容时显示。

    OFS:Output Field Separator 输出分隔符

举个栗子

1. 指定字段分隔符

# 取出网卡IP地址(IP已经过处理)
[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.2??.???.??/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.2??.???.??

2. 指定记录分隔符

[root@localhost ~]# 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@localhost ~]# 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 命令重新指定:为行结尾

3. 指定输出分隔符

[root@localhost ~]# 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

内置变量

$NR    行号
$NF    最后一列  

二、运行模式

1. 正则表达式作为模式

例题一

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:

打印 Facebook 的所在行

[root@localhost ~]# awk '$2~/Facebook/' value.list
06 Facebook 5517.97 USA INTERNET
问题 1.1:

打印 Facebook排名市值

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

显示以A开头的企业所在行

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

显示以A开头的企业全名市值

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

小结及扩展:

awk 中使用正则表达式作为条件,也可以用 $列~ 的形式用来精准匹配。
'//' 寻找字符
'$3~//' 在第三列中寻找字符
~ 匹配
!~ 不匹配
输出的结果可能部分小伙伴觉得不美观,可以使用 column -t 对输出结果进行对齐优化,在之前的例题中都可以使用。

补充 :

[root@localhost ~]# cat value.list | column -t
#排名  名称               市值(亿美元)         所在国家  行业
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

问题 3:

[root@localhost ~]# awk 'NR==2' value.list
NO. NAME VALUE(BillionDollars) Country Industry
[root@localhost ~]# awk 'NR==3' value.list
01 Apple 8858.88 USA CONSUMER ELECTRONICS
[root@localhost ~]# awk 'NR==3{print $0}' value.list
01 Apple 8858.88 USA CONSUMER ELECTRONICS
[root@localhost ~]# awk 'NR==3{print}' value.list
01 Apple 8858.88 USA CONSUMER ELECTRONICS

从此例题可以看出 $0 为整行,且 print 的默认参数就是 $0

问题 4:

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

[root@localhost ~]# 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 全局替换

命令格式:

gsub(/要找的内容/,"替换为什么",列)

例题二:

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

:全部换成$

[root@localhost ~]# awk '{gsub(/:/,"$",$NF);print}' donate.txt 
TOM $334$224
CAIN $776$633
JANE $375$200

2. 比较表达式为模式

问题 1:

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

[root@localhost ~]# 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

提示:其他可用比较表达式>>=<<===!=

3. 指定范围模式

例题二:

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

问题 1:

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

[root@localhost ~]# 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;
}

4. BEGIN{} / END{} 模式

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

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

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

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

例题三:

统计 /etc/serivces 文件中空行的数量

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

例题四:

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

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

例题五:

统计某文件的某列的总和

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

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

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

例题六:

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

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

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

[root@localhost ~]# 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列 浏览器版本、系统类型、渲染引擎等

三、数组

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

可以使用常规手段

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

现在尝试使用数组进行实现

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

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

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

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

[root@localhost ~]# 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]存储每个三级域名出现的次数,然后打印即可。

例题八:

8.1 分析系统登录日志,检查是否存在暴力破解行为,并统计每个IP的破解次数。
附表:例题八练习文件点此下载

[root@localhost ~]# 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@localhost ~]# 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

8.2 分析每个用户被破解的次数

[root@localhost ~]# 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

例题九:

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

[root@localhost ~]# 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

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

[root@localhost ~]# 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

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

[root@localhost ~]# 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

参考链接

回复