第1章 awk基础入门
1.1 awk简介
awk不仅仅时linux系统中的一个命令,而且是一种编程语言,可以用来处理数据和生成报告(excel)。处理的数据可以是一个或多个文件,可以是来自标准输入,也可以通过管道获取标准输入,awk可以在命令行上直接编辑命令进行操作,也可以编写成awk程序来进行更为复杂的运用。本章主要讲解awk命令的运用
1.2 awk环境简介
[root@chensiqi1 ~]# cat /etc/redhat-release CentOS release 6.8 (Final) [root@chensiqi1 ~]# uname -r2.6.32-642.el6.x86_64 [root@chensiqi1 ~]# ll `which awk`lrwxrwxrwx. 1 root root 4 Dec 23 20:25 /bin/awk -> gawk [root@chensiqi1 ~]# awk --versionGNU Awk 3.1.7Copyright (C) 1989, 1991-2009 Free Software Foundation.
1.3 awk的格式
awk指令是由模式,动作,或者模式和动作的组合组成。
模式既pattern,可以类似理解成sed的模式匹配,可以由表达式组成,也可以是两个正斜杠之间的正则表达式。比如NR==1,这就是模式,可以把他理解为一个条件。
动作即action,是由在大括号里面的一条或多条语句组成,语句之间使用分号隔开。比如awk使用格式:
1.4 模式动作
示例1-1: 基本的模式和动作
[root@chensiqi1 ~]# awk -F ":" 'NR>=2 && NR<=6{print NR,$1}' /etc/passwd2 bin3 daemon4 adm5 lp6 sync 命令说明: -F 指定分隔符为冒号,相当于以“:”为菜刀,进行字段的切割。 NR>=2 && NR<=6:这部分表示模式,是一个条件,表示取第2行到第6行。 {print NR,$1}:这部分表示动作,表示要输出NR行号和$1第一列。
示例1-2 只有模式
[root@chensiqi1 ~]# awk -F ":" 'NR>=2&&NR<=6' /etc/passwdbin:x:1:1:bin:/bin:/sbin/nologindaemon:x:2:2:daemon:/sbin:/sbin/nologinadm:x:3:4:adm:/var/adm:/sbin/nologinlp:x:4:7:lp:/var/spool/lpd:/sbin/nologinsync:x:5:0:sync:/sbin:/bin/sync命令说明: -F指定分隔符为冒号 NR>=2&&NR<=6这部分是条件,表示取第2行到第6行。 但是这里没有动作,这里大家需要了解如果只有条件(模式)没有动作,awk默认输出整行
示例1-3:只有动作
[root@chensiqi1 ~]# awk -F ":" '{print NR,$1}' /etc/passwd1 root2 bin3 daemon4 adm5 lp6 sync7 shutdown8 halt9 mail10 uucp 以下省略.... 命令说明: -F指定分隔符为冒号 这里没有条件,表示对每一行都处理 {print NR,$1}表示动作,显示NR行号与$1第一列 这里要理解没有条件的时候,awk会处理每一行。
示例1-4:多个模式和动作
[root@chensiqi1 ~]# awk -F ":" 'NR==1{print NR,$1}NR==2{print NR,$NF}' /etc/passwd1 root2 /sbin/nologin 命令说明: -F指定分隔符为冒号 这里有多个条件与动作的组合 NR==1表示条件,行号(NR)等于1的条件满足的时候,执行{print NR,$1}动作,输出行号与第一列。 NR==2表示条件,行号(NR)等于2的条件满足的时候,执行{print NR,$NF}动作,输出行号与最后一列($NF)
注意:
Pattern和{Action}需要用单引号引起来,防止shell作解释。
Pattern是可选的。如果不指定,awk将处理输入文件中的所有记录。如果指定一个模式,awk则只处理匹配指定的模式的记录。
{Action}为awk命令,可以是但个命令,也可以多个命令。整个Action(包括里面的所有命令)都必须放在{ 和 }之间。
Action必须被{ }包裹,没有被{ }包裹的就是Patern
file要处理的目标文件
1.5 记录和字段
接下来我给大家带来两个新概念记录和字段,这里为了方便大家理解可以把记录就当作行即记录==行,字段相当于列,字段==列。
名称 | 含义 |
---|
record | 记录,行 |
field | 域,区域,字段,列 |
1.5.1 记录(行)
查看一下下面这段文字
root:x:0:0:root:/root:/bin/bashbin:x:1:1:bin:/bin:/sbin/nologindaemon:x:2:2:daemon:/sbin:/sbin/nologinadm:x:3:4:adm:/var/adm:/sbin/nologinlp:x:4:7:lp:/var/spool/lpd:/sbin/nologinsync:x:5:0:sync:/sbin:/bin/syncshutdown:x:6:0:shutdown:/sbin:/sbin/shutdownhalt:x:7:0:halt:/sbin:/sbin/haltmail:x:8:12:mail:/var/spool/mail:/sbin/nologinuucp:x:10:14:uucp:/var/spool/uucp:/sbin/nologin
思考:
一共有多少行呢?你如何知道的?通过什么标志?
awk对每个要处理的输入数据认为都是具有格式和结构的,而不仅仅是一堆字符串。默认情况下,每一行内容都是一条记录,并以换行符分隔(\n)结束
1.5.2 企业面试题:按单词出现频率降序排序(计算文件中每个单词的重复数量)
注:(此处使用sort与uniq即可)
题目:
题目创建方法:
sed -r '1,10s#[^a-zA-Z]+# #g' /etc/passwd>/server/files/count.txt
[root@chensiqi1 files]# cat /server/files/count.txt root x root root bin bash bin x bin bin sbin nologin daemon x daemon sbin sbin nologin adm x adm var adm sbin nologin lp x lp var spool lpd sbin nologin sync x sync sbin bin sync shutdown x shutdown sbin sbin shutdown halt x halt sbin sbin halt mail x mail var spool mail sbin nologin uucp x uucp var spool uucp sbin nologin
思路:
让所有单词排成一列,这样每个单词都是单独的一行
1)设置RS值为空格
2)将文件里面的所有空格替换为回车换行符“\n”
3)grep所有连续的字母,grep -o参数让他们排成一列。
1.6 字段与记录小结
现在你应该会对awk的记录字段有所了解了,下面我们总结一下,学会给阶段性知识总结是学好运维的必备技能。
RS记录分隔符,表示每行的结束标志
NR行号(记录号)
FS字段分隔符,每列的分隔标志或结束标志
NF就是每行有多少列,每个记录中字段的数量
$符号表示取某个列(字段),$1$2$NF
NF表示记录中的区域(列)数量,$NF
取最后一个列(区域。)
FS(-F)字段(列)分隔符,-F(FS)“:”<==>‘BEGIN{FS=':'}’
RS 记录分隔符(行的结束标识)
NR 行号
选好合适的刀FS(***),RS,OFS,ORS
分隔符==>结束标识
记录与区域,你就对我们所谓的行与列,有了新的认识(RS,FS)
第2章 awk进阶
2.1 awk模式与动作
接下来就详细介绍下,awk的模式都有几种:
正则表达式作为模式
比较表达式作为模式
范围模式
特殊模式BEGIN和END
awk的模式是你玩好awk的必备也是最基础的内容,必须熟练掌握
2.2 正则表达式作为模式
awk同sed一样也可以通过模式匹配来对输入的文本进行匹配处理。说到模式匹配,肯定少不了正则表达式,awk也支持大量的正则表达式模式,大部分与sed支持的元字符类似,而且正则表达式是玩转三剑客的必备工具,下表列出了awk支持的正则表达式元字符:
awk默认就支持的元字符:
元字符 | 功能 | 示例 | 解释 |
---|
^ | 字符串开头 | /^chensiqi/或\$3~/^chensiqi/ | 匹配所有以chensiqi开头的字符串;匹配出所有第三列中以chensiqi开头的 |
\$ | 字符串结尾 | /chensiqi$/或$3~/chensiqi$/ | 匹配所有以chensiqi结尾的字符串;匹配第三列中以chensiqi结尾的 |
.(点) | 匹配任意但个字符(包括回车符) | /c..l/ | 匹配字母c,然后两个任意字符,再以l结尾的行 |
* | 重复0个或多个前一个字符 | /a*cool/ | 匹配0个或多个a之后紧跟着cool的行 |
+ | 重复前一个字符一次或多次 | /a+b/ | 匹配一个或多个a加上字符串b的行 |
? | 匹配0个或一个前边的字符 | /a?b/ | 匹配以字母a或b或c开头的行 |
[] | 匹配指定字符组内的任一个字符 | /^[abc]/ | 匹配以字母a或b或c开头的行 |
[^] | 匹配不在指定字符组内的任一字符 | /^[^abc]/ | 匹配不以字母a或b或c开头的行 |
() | 子表达式组合 | /(chensiqi)+/ | 表示一个或多个cool组合,当有一些字符需要组合时,使用括号括起来 |
| | 或者的意思 | /(chensiqi)|B/ | 匹配chensiqi或字母B的行 |
awk默认不支持的元字符:(参数--posix)
元字符 | 功能 | 示例 | 解释 |
---|
x{m} | x字符重复m次 | /cool{5}/ | 匹配l字符5次 |
x{m,} | x字符重复至少m次 | /(cool){2,}/ | 匹配cool整体,至少2次 |
x{m,n} | x字符重复至少m次,但不超过n次 | /(cool){5,6}/ | 匹配cool整体,至少5次,最多6次 |
提示:
加括号代表整体匹配,不加那么就匹配前边的一个字符。awk默认不支持这种形式的正则,需要加--posix参数或者--re-interval
正则表达式的运用,默认是在行内查找匹配的字符串,若有匹配则执行action操作,但是有时候仅需要固定的列来匹配指定的正则表达式,比如:我想取/etc/passwd文件中第五列{$5}这一列查找匹配mail字符串的行,这样就需要用另外两个匹配操作符,并且awk里面只有这两个操作符来匹配正则表达式。
2.2.1 awk正则匹配操作符
awk正则匹配操作符:
|~|用于对记录或区域的表达式进行匹配|
|--|--|
|!~|用于表达与~相反的意思|
下面还是通过具体示例来看看,awk如何来通过正则表达式匹配字符串的
2.2.2 awk正则表达式匹配整行
[root@chensiqi1 files]# awk -F ":" '/^root/' awkfile.txt root:x:0:0:root:/root:/bin/bash
和下面的效果是一样的
[root@chensiqi1 files]# awk -F ":" '$0~/^root/' awkfile.txt root:x:0:0:root:/root:/bin/bash
提示:
awk只用正则表达式的时候是默认匹配整行的即‘$0~/^root/’和‘/^root/’是一样的。
2.2.3 awk正则表达式匹配一行中的某一列
[root@chensiqi1 files]# awk -F ":" '$5~/shutdown/' awkfile.txt shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
提示:
合并在一起
$5~/shutdown/表示第五个区域(列)匹配正则表达式/shutdown/,既第5列包含shutdown这个字符串,则显示这一行。
2.2.4 某个区域中的开头和结尾
知道了如何使用正则表达式匹配操作符之后,我们来看看awk正则与grep和sed不同的地方。
awk正则表达式
|^|匹配一个字符串的开头|
|--|--|
|$|匹配一个字符串的结尾|
在sed和grep这两个命令中,我们都把它们当作行的开头和结尾。但是在awk中他表示的是字符串的开头和结尾。
接下来我们通过练习题来联系awk如何使用正则表达式。
2.2.5 创建测试环境
说明:
第一列是姓氏
第二列是名字
第一列第二列合起来就是姓名
第三列是对应的ID号码
最后三列是三次捐款数量
2.2.6 awk正则表达式练习题-详解
示例1:显示姓Zhang的人的第二次捐款金额及她的名字
说明:
-F指定分隔符,现在大家知道了-F即FS也是支持正则表达式的。
【 :】+ 表示连续的空格或冒号
-F “【 :】+” 以连续的空格或冒号为分隔符
/Zhang/表示条件,整行中包含Dan字符的这个条件。
{print $1,$(NF-1)} 表示动作,满足条件后,执行显示第一列($1)和倒数第二列($(NF-1))当然$5也可以。
注意:
NF是一行中有多少列,NF-1整行就是倒数第二列。
$(NF-1)就是取倒数第二列内容。
示例2:显示Xiaoyu的姓氏和ID号码
示例3:显示所有以41开头的ID号码的人的全名和ID号码
示例4:显示所有以一个D或X开头的人名全名
注意:
这里要用()括号表示即^(D|X)相当于^D|^X,有的同学写成^D|X这样是错误的。
示例5:显示所有ID号码最后一位数字是1或5的人的全名
示例6:显示Xiaoyu的捐款,每个值都有以$开头。如$520$200$135
示例7:显示所有人的全名,以姓,名的格式显示,如Meng,Feixue
2.2.7 企业面试题:取出网卡eth0的ip地址
最简单:hostname -I
awk处理:
方法一:
[root@chensiqi1 files]# ifconfig eth0|awk 'BEGIN{RS="[ :]"}NR==31'192.168.197.133
方法二:
[root@chensiqi1 files]# ifconfig eth0 | awk -F "(addr:)|( Bcast:)" 'NR==2{print $2}'192.168.197.133
方法三:
[root@chensiqi1 files]# ifconfig eth0 | awk -F "[ :]+" 'NR==2{print $4}'192.168.197.133
方法四:
[root@chensiqi1 files]# ifconfig eth0 | awk -F "[^0-9.]+" 'NR==2{print $2}'192.168.197.133
2.2.8 awk正则之{} -花括号
awk中的花括号有些不常用,但是偶尔会用到这里简单介绍。
示例:取出awkfile中第一列包含一个o或者两个o的行
2.2.9 企业案例1:取出常用服务端口号
思路:
linux下面服务与端口信息的对应表格在/etc/services里面,所以这道题要处理/etc/services文件。
我们简单分析以下servics文件:
从23行开始基本上每一行第一列是服务名称,第二列的第一部分是端口号,第二列的第二部分是tcp或udp协议。
方法:
[root@chensiqi1 ~]#
awk -F "[ /]+" '$1~/^(ssh)$|^(http)$|^(https)$|^(mysql)$|^(ftp)$/{print $1,$2}' /etc/services |sort|uniq
ftp 21
http 80
https 443
mysql 3306
ssh 22
2.3 比较表达式做为模式-需要一些例子
awk是一种编程语言,能够进行更为复杂的判断,当条件为真时候,awk就执行相关的action。主要是针对某一区域做出相关的判断,比如打印成绩在80分以上的行,这样就必须对这一区域做比较判断,下表列出了awk可以使用的关系运算符,可以用来比较数字字符串,还有正则表达式。当表达式为真时候,表达式结果1,否0,只有表达式为真,awk才执行相关的action
运算符 | 含义 | 示例 |
---|
< | 小于 | x>y |
<= | 小于等于 | x<=y |
== | 等于 | x==y |
!= | 不等于 | x!=y |
>= | 大于或等于 | x>=y |
> | 大于 | x<y |
以上运算符是针对数字的,下面两个运算符之前已有示例,针对字符串
~ | 与正则表达式匹配 | x~/y/ |
---|
!~ | 与正则表达式不匹配 | x!~y |
2.3.1 企业面试题:取出文件/etc/services的23~30行
思路:
想表示一个范围,一个行的范围,就要用到NR这个内置变量了,同时也要用到比较表达式。
答案:
[root@www ~]# awk 'NR>=23&&NR<=30' /etc/services
[root@www ~]# awk 'NR>22&&NR<31' /etc/services
说明:
1)比较表达式比较常用的还是表示大于等于,小于等于或者等于,根据这个例子来学习即可
2)NR表示行号,大于等于23即,NR>=23小于等于30,即NR<=30
3)合起来就是NR>=23并且NR<=30,&&表示并且,同时成立的意思。
4)换一种表达式方法就是大于22行小于31行,即NR>22&&NR<31
2.3.2 如果判断某一列是否等于某个字符呢?
示例:找出/etc/passwd中第五列是root的行
测试文件:
答案:
2.4 范围模式
pattern1 |
| pattern2 |
---|
从哪里来 | 到 | 哪里去 |
条件1 |
| 条件2 |
范围模式简单理解就是从哪里来,到哪里去。
匹配从条件1开始到条件2介绍的范围
1)还记得sed使用地址范围来处理文本内容嘛?awk的范围模式,与sed类似,但是又有不同,awk不能直接使用行号来作为范围起始地址,因为awk具有内置变量NR来存储记录号,所有需要使用NR=1,NR=5这样来使用。
2)范围模式处理的原则是:先匹配从第一个模式的首次出现到第二个模式的首次出现之间的内容,执行action。然后匹配从第一个模式的下一次出现到第二个模式的下一次出现,直到文本结束。如果匹配到第一个模式而没有匹配到第二个模式,则awk处理从第一个模式开始直到文本结束全部的行。如果第一个模式不匹配,就算第二个模式匹配,awk依旧不处理任何行。
awk '/start pos/,/end pos/{print $)} passwd chensiqi' awk '/start pos/,NR==XXX{print $0}' passwd chensiqi
范围模式的时候,范围条件的时候,表达式必须能匹配一行。
说明:
条件是:从第二行,到第五行
动作是:显示行号(NR)和整行($0)
合起来就是显示第二行到第五行的行好和整行的内容
示例2:
说明:
条件是:从以bin开头的行,到第五行
动作是:显示行号和整行内容
合起来就是显示从以bin开头的行,到第五行中的行号和整行内容。
示例3:
说明:
条件:从第五列以bin开头的行到以lp开头的行
动作:显示行号和正航内容
合起来:从第三列以bin开始的行到以lp开头的行并显示其行号和整行内容
说明:
条件:从第三列以bin开头字符串的行到第三列以lp开头字符串的行
动作:显示行号和整行
2.5 awk特殊模式-BEGIN模式与END模式
BEGIN模块再awk读取文件之前就执行,一般用来定义我们的内置变量(预定义变量,eg:FS,RS),可以输出表头(类似excel表格名称)
BEGIN模式之前我们有在示例中提到,自定义变量,给内容变量赋值等,都使用过。需要注意的是BEGIN模式后面要接跟一个action操作块,包含在大括号内。awk必须在输入文件进行任何处理前先执行BEGIN里的动作(action)。我们可以不要任何输入文件,就可以对BEGIN模块进行测试,因为awk需要先执行完BEGIN模式,才对输入文件做处理。BEGIN模式常常被用来修改内置变量ORS,RS,FS,OFS等值。
2.5.1 BEGIN模块
1)第一个作用,内置变量的定义
示例:取eth0的IP地址
答案:
2)第二个作用,在读取文件之前,输出些提示性信息(表头)。
说明:
要在第一行输出一些username和UID,我们应该想到BEGIN{}这个特殊的条件(模式),因为BEGIN{}在awk读取文件之前执行的。
所以结果是BEGIN{print "username","UID"}
,注意print命令里面双引号吃啥吐啥,原样输出。
然后我们实现了在输出文件内容之前输出“username”和“UID”,下一步输出文件的第一列和第三列即{print $1,$3}
最后结果就是BEGIN{print "username","UID"}{print $1,$3}
3)第三个作用,使用BEGIN模块的特殊性质,进行一些测试。
2.5.2 awk中变量的概念简介
说明:
没有文件awk依旧可以处理BEGIN模式下的动作(命令)
2.5.3 END模块
EHD在awk读取完所有的文件的时候,再执行END模块,一般用来输出一个结果(累加,数组结果),也可以是和BEGIN模块类似的结尾标识信息
与BEGIN模式相对应的END模式,格式一样,但是END模式仅在awk处理完所有输入行后才进行处理。
企业案例:统计/etc/servies文件里的空行数量
思路:
a)空行通过正则表达式来实现:^$
b)统计数量:
方法一:grep
方法二:
[root@chensiqi files]# awk '/^$/{i++}END{print i}' /etc/services
16
提示:
使用了awk的技术功能,很常用
第一步:统计空行个数
/^$/
表示条件,匹配出空行,然后执行{i++}(i++等于i=i+1)即:/^$/{i=i+1}
我们可以通过/^$/{i=i+1;print i}
来查看awk执行过程
第二步:输出最后结果
所以最终结果就是awk '/^$/{i=i+1}END{print "blank lines count:"i}' /etc/services
企业面试题5:文件count.txt,文件内容是1到100(由seq 100生成),请计算文件每行值加起来的结果(计算1+...+100)
思路:
文件每一行都有且只有一个数字,所以我们要让文件的每行内容相加。
回顾一下上一道题我们用的是i++即i=i+1
这里我们需要使用到第二个常用的表达式
i=i+$0
对比一下,其实只是把上边的1换成了$0
[root@chensiqi files]# awk '{i=i+$0}END{print i}' count.txt 5050
2.7 awk数组
awk提供了数组来存放一组相关的值。
awk是一种编程语言,肯定也支持数组的运用,但是又不同于c语言的数组。数组在awk中被称为关联数组,因为它的下标既可以是数字也可以是字符串。下标通常被称作key,并且与对应的数组元素的值关联。数组元素的key和值都存储在awk程序内部的一张表中,通过一定散列算法来存储,所以数组元素都不是按顺序存储的。打印出来的顺序也肯定不是按照一定的顺序,但是我们可以通过管道来对所需的数据再次操作来达到自己的效果。
如图不难发现,awk数组就和酒店一样。数组的名称就像是酒店名称,数组元素名称就像酒店房间号码,每个数组元素里面的内容就像是酒店房间里面的人。
2.8 图片-数组
假设我们有一个酒店
酒店<===>chensiqihotel
酒店里面有几个房间110,119,120,114这几个房间
酒店110房间<===>chensiqihotel[110] 酒店120房间<===>chensiqihotel[120] 酒店119房间<===>chensiqihotel[119] 酒店114房间<===>chensiqihotel[114] 酒店房间里面入住客人
酒店110房间住着xiaoyu<===>chensiqihotel[110]="xiaoyu"
酒店119房间住着ruxue<===>chensiqihotel[119]="ruxue"
酒店120房间住着dandan<===>chensiqihotel[120]="dandan"
酒店114房间住着waiwai<===>chensiqihotel[114]="waiwai"
示例:
"xiaoyu"
[root@chensiqi ~]# awk '
xiaoyu
dandan
waiwai
ruxue
企业面试题1:统计域名访问次数
处理以下文件内容,将域名取出并根据域名进行计数排序处理:(百度和sohu面试题)
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
思路:
1)以斜线为菜刀取出第二列(域名)
2)创建一个数组
3)把第二列(域名)作为数组的下标
4)通过类似于i++的形式进行计数
5)统计后把结果输出
过程演示:
第一步:查看一下内容
[root@chensiqi ~]# awk -F "[/]+" '{print $2}' file www.etiantian.org www.etiantian.org post.etiantian.org mp3.etiantian.org www.etiantian.org post.etiantian.org
第二步:计数
[root@chensiqi ~]# awk -F "[/]+" '{i++;print $2,i}' file
www.etiantian.org 1
www.etiantian.org 2
post.etiantian.org 3
mp3.etiantian.org 4
www.etiantian.org 5
post.etiantian.org 6
命令说明: i++:i最开始是空的,当awk读取一行,i自身+1
第三步:用数组替换i
[root@chensiqi ~]# awk -F "[/]+" '{h[$2]++;print $2,h["www.etiantian.org"]}' file www.etiantian.org 1
www.etiantian.org 2
post.etiantian.org 2
mp3.etiantian.org 2
www.etiantian.org 3
post.etiantian.org 3
命令说明:
1)将i替换成h[$2];相当于我创建了一个数组h[],然后用$2作为我的房间号。但是目前房间里是没有东西的。也就是说h[$2]=h["www.etiantian.org"] and h["post.etiantian.org"] and h["mp3.etiantian.org"] 但是具体房间里是没有东西的也就是空。
2)h[$2]++就等于i++:也就是说我开始给房间里加东西;当出现同样的东西,我就++
3)print h["www.etiantian.org"]:意思就是说我开始要输出了。我要输出的是房间号为“www.etiantian.org”里面的内容。这里面的内容最早是空的,随着awk读取每一行一旦出现房间号为“www.etiantian.org”的房间时,我就给房间里的内容进行++。
4)综上,输出的结果中,每次出现www.etiantian.org时,h["www.etiantian.org"]就会++。因此最后的输出数字是3
第四步:输出最终计数结果
[root@chensiqi ~]# awk -F "[/]+" '{h[$2]++}END{for(i in h)print i,h[i]}' file mp3.etiantian.org 1
post.etiantian.org 2
www.etiantian.org 3
[root@chensiqi ~]# 命令说明: 我们最终需要输出的是去重复以后的统计结果,所以得在END模块里进行输出
for(i in h)遍历这个数组,i里存的都是房间号
print i,h[i]:输出每一个房间号及其房间里的内容(计数结果)
提示:
awk的应用里最重要的一个功能就是计数,而数组在awk里最大的作用就是去重复。