您的位置:首页 > 运维架构 > Shell

第八章 Shell文本处理三剑客之awk

2017-11-24 08:59 162 查看

上节讲了grep、sed工具,已经能满足常见的文本处理需求,但有些需求对于他们来说心有余而力不足,今天所讲的工具就能完成他们大多数的功能,它就是三剑客中的老大AWK,我相信一定不会让你失望,下面一起看看吧!

本章大纲:





8.3 awk

awk是一个处理文本的编程语言工具,能用简短的程序处理标准输入或文件、数据排序、计算以及生成报表等等。
在Linux系统下默认awk是gawk,它是awk的GNU版本。可以通过命令查看应用的版本:ls -l /bin/awk
基本的命令语法:awk option 'pattern {action}' file
其中pattern表示AWK在数据中查找的内容,而action是在找到匹配内容时所执行的一系列命令。花括号用于根据特定的模式对一系列指令进行分组。
awk处理的工作方式与数据库类似,支持对记录和字段处理,这也是grep和sed不能实现的。
在awk中,缺省的情况下将文本文件中的一行视为一个记录,逐行放到内存中处理,而将一行中的某一部分作为记录中的一个字段。用1,2,3...数字的方式顺序的表示行(记录)中的不同字段。用$后跟数字,引用对应的字段,以逗号分隔,0表示整个行。

8.3.1 选项

选项
描述
-f program-file
从文件中读取awk程序源文件
-F fs
指定fs为输入字段分隔符
-v var=value
变量赋值
--posix
兼容POSIX正则表达式
--dump-variables=[file]
把awk命令时的全局变量写入文件,
默认文件是awkvars.out
--profile=[file]
格式化awk语句到文件,默认是awkprof.out

8.3.2 模式

常用模式有:
Pattern
Description
BEGIN{ }
给程序赋予初始状态,先执行的工作
END{ }
程序结束之后执行的一些扫尾工作
/regular expression/
为每个输入记录匹配正则表达式
pattern && pattern
逻辑and,满足两个模式
pattern || pattern
逻辑or,满足其中一个模式
! pattern
逻辑not,不满足模式
pattern1, pattern2
范围模式,匹配所有模式1的记录,直到匹配到模式2
而动作呢,就是下面所讲的print、流程控制、I/O语句等。
示例:
1)从文件读取awk程序处理文件

2)指定分隔符,打印指定字段

还可以指定多个分隔符,作为同一个分隔符处理:

[]元字符的意思是符号其中任意一个字符,也就是说每遇到一个/或#时就分隔一个字段,当用多个分隔符时,就能更方面处理字段了。
3)变量赋值

4)输出awk全局变量到文件

5)BEGIN和END
BEGIN模式是在处理文件之前执行该操作,常用于修改内置变量、变量赋值和打印输出的页眉或标题。
例如:打印页眉

END模式是在程序处理完才会执行。
例如:打印页尾

6)格式化输出awk命令到文件

7)/re/正则匹配

8)逻辑and、or和not

9)匹配范围

8.3.3 内置变量

变量名
描述
FS
输入字段分隔符,默认是空格或制表符
OFS
输出字段分隔符,默认是空格
RS
输入记录分隔符,默认是换行符\n
ORS
输出记录分隔符,默认是换行符\n
NF
统计当前记录中字段个数
NR
统计记录编号,每处理一行记录,编号就会+1
FNR
统计记录编号,每处理一行记录,编号也会+1,与NR不同的是,处理第二个文件时,编号会重新计数。
ARGC
命令行参数数量
ARGIND
当前正在处理的文件索引值。第一个文件是1,第二个文件是2,以此类推
ARGV
命令行参数数组序列数组,下标从0开始,ARGV[0]是awk
ENVIRON
当前系统的环境变量
FILENAME
输出当前处理的文件名
IGNORECASE
忽略大小写
SUBSEP
数组中下标的分隔符,默认为"\034"
示例:

1)FS和OFS
在程序开始前重新赋值FS变量,改变默认分隔符为冒号,与-F一样。

2)RS和ORS
RS默认是\n分隔每行,如果想指定以某个字符作为分隔符来处理记录:

3)NF

NF是打印字段个数。

4)NR和FNR
NR统计记录编号,每处理一行记录,编号就会+1,FNR不同的是在统计第二个文件时会重新计数。

看下NR和FNR的区别:

可以看出NR每处理一行就会+1,而FNR在处理第二个文件时,编号重新计数。同时也知道awk处理两个文件时,是合并到一起处理。

当FNR==NR时,说明在处理第一个文件内容,不等于时说明在处理第二个文件内容。
一般FNR在处理多个文件时会用到,下面会讲解。
5)ARGC和ARGV
ARGC是命令行参数数量
ARGV是将命令行参数存到数组,元素由ARGC指定,数组下标从0开始

6)ARGIND
ARGIND是当前正在处理的文件索引值,第一个文件是1,第二个文件是2,以此类推,从而可以通过这种方式判断正在处理哪个文件。

7)ENVIRON
ENVIRON调用系统变量。

8)FILENAME
FILENAME是当前处理文件的文件名。

等于1代表忽略大小写。

8.3.4 操作符

运算符
描述
(....)
分组
$
字段引用
++ --
递增和递减
+ - !
加号,减号,和逻辑否定
* / %
乘,除和取余
+ -
加法,减法
| |&
管道,用于getline,print和printf
< > <= >= != ==
关系运算符
~ !~
正则表达式匹配,否定正则表达式匹配
in
数组成员
&& ||
逻辑and,逻辑or
?:
简写条件表达式:
expr1 ? expr2 : expr3
第一个表达式为真,执行expr2,否则执行expr3
= += -= *= /= %= ^=
变量赋值运算符
须知:在awk中,有3种情况表达式为假:数字是0,空字符串和未定义的值
数值运算,未定义变量初始值为0。字符运算,未定义变量初始值为空。
举例测试:

示例:
1)截取整数

2)感叹号

2)不匹配某行

3)乘法和除法

4)管道符使用

5)正则表达式匹配

6)判断数组成员

7)三目运算符

8)变量赋值

8.3.5 流程控制

1)if语句
格式:if(condition) statement [ else statement ]

2)while语句
格式:while(condition) statement

3)for语句C语言风格
格式:for(expr1; expr2; expr3) statement

5)break和continue语句
break跳过所有循环,continue跳过当前循环。

6)删除数组和元素
格式:
deletearray[index] 删除数组元素
deletearray 删除数组

7)exit语句
格式:exit[ expression ]
exit退出程序,与shell的exit一样。[ expr]是0-255之间的数字。

8.3.6 数组

数组是用来存储一系列值的变量,通过下标(索引)来访问值。
awk中数组称为关联数组,不仅可以使用数字作为下标,还可以使用字符串作为下标。
数组元素的键和值存储在awk程序内部的一个表中,该表采用散列算法,因此数组元素是随机排序。
数组格式:array[index]=value
1)自定义数组

2)通过NR设置记录下标,下标从1开始

3)通过for循环遍历数组

上面打印的i是数组的下标。
第一种for循环的结果是乱序的,刚说过,数组是无序存储。
第二种for循环通过下标获取的情况是排序正常。
所以当下标是数字序列时,还是用for(expr1;expr2;expr3)循环表达式比较好,保持顺序不变。
4)通过++方式作为下标

x被awk初始化值是0,没循环一次+1
5)使用字段作为下标

6)统计相同字段出现次数

第一个字段作为下标,值被++初始化是0,每次遇到下标(第一个字段)一样时,对应的值就会被+1,因此实现了统计出现次数。
想要实现去重的的话就简单了,只要打印下标即可。
7)统计TCP连接状态

8)只打印出现次数大于等于2的

9)去重

只打印重复的行说明:先明白一个情况,当值是0是为假,1为真,知道这点就不难理解了。由于执行了++当处理第一条记录时,初始值是0为假,就不打印,如果再遇到相同的记录,值就会+1,不为0,打印。
去重说明:初始值是0为假,感叹号取反为真,打印,也就是说,每个记录的第一个值都是为0,所以都会打印,如果再遇到相同的记录+1,值就会为真,取反为假就不打印。

10)统计每个相同字段的某字段总数:

11)多维数组
awk的多维数组,实际上awk并不支持多维数组,而是逻辑上模拟二维数组的访问方式,比如a[a,b]=1,使用SUBSEP(默认\034)作为分隔下标字段,存储后是这样a\034b。
示例:

8.3.7 内置函数

函数
描述
int(expr)

截断为整数

sqrt(expr)

平方根

rand()

返回一个随机数N,0和1范围,0 < N < 1

srand([expr])

使用expr生成随机数,如果不指定,默认使用当前时间为种子,如果前面有种子则使用生成随机数

asort(a, b)

对数组a的值进行排序,把排序后的值存到新的数组b中,新排序的数组下标从1开始

asorti(a,b)

对数组a的下标进行排序,同上

sub(r, s [, t])

对输入的记录用s替换r,t可选针对某字段替换 ,但只替换第一个字符串

gsub(r,s [, t])

对输入的记录用s替换r,t可选针对某字段替换,替换所有字符串

index(s, t)

返回s中字符串t的索引位置,0为不存在

length([s])

返回s的长度

match(s, r [, a])

测试字符串s是否包含匹配r的字符串

split(s, a [, r [, seps] ])

根据分隔符seps将s分成数组a

substr(s, i [, n])

截取字符串s从i开始到长度n,如果n没指定则是剩余部分

tolower(str)

str中的所有大写转换成小写

toupper(str)

str中的所有小写转换成大写

systime()

当前时间戳

strftime([format [, timestamp[, utc-flag]]])

格式化输出时间,将时间戳转为字符串

示例:

1)int()

2)sqrt()

获取9的平方根:

3)rand()和srand()

如果想更完美生成随机数,还得做相应的处理!

4)asort()和asorti()

asort将a数组的值放到数组b,a下标丢弃,并将数组b的总行号赋值给s,新数组b下标从1开始,然后遍历。

5)sub()和gsub()

在指定行前后加一行:

6)index()

7)length()

8)split()

9)substr()

10)tolower()和toupper()

11)时间处理

8.3.8 I/O语句

语句
描述
getline

设置$0来自下一个输入记录

getline var

设置var来自下一个输入记录

command | getline [var]

运行命令管道输出到$0或var

next

停止当前处理的输入记录

print

打印当前记录

printf fmt, expr-list

格式化输出

printf fmt, expr-list >file

格式输出和写到文件

system(cmd-line)

执行命令和返回状态

print ... >> file

追加输出到文件

print ... | command

打印输出作为命令输入

示例:

1)getline

2)getline var

4)next

5)system()

6)打印结果写到文件

7)管道连接shell命令

8.3.9 printf语句

格式化输出,默认打印字符串不换行。

格式:printf [format] arguments

Format
描述
%s

一个字符串

%d,%i一个小数

%f一个浮点数
%.ns

输出字符串,n是输出几个字符

%ni

输出整数,n是输出几个数字

%m.nf

输出浮点数,m是输出整数位数,n是输出的小数位数

%x

不带正负号的十六进制,使用a至f表示10到15

%X

不带正负号的十六进制,使用A至F表示10至15

%%

输出单个%

%-5s

左对齐,对参数每个字段左对齐,宽度为5

%-4.2f

左对齐,宽度为4,保留两位小数

%5s

右对齐,不加横线表示右对齐

示例:

8.3.10 自定义函数

格式:function name(parameter list) { statements }

示例:

8.3.11 需求案例

1)分析Nginx日志

日志格式:'$remote_addr - $remote_user [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent" "$http_x_forwarded_for"'

2)两个文件对比

找出b文件在a文件相同记录:

3)合并两个文件

将a文件合并到b文件:

将a文件相同IP的服务名合并:

说明:数组a存储是$1=a[$1] $2,第一个a[$1]是以第一个字段为下标,值是a[$1] $2,也就是$1=a[$1] $2,值的a[$1]是用第一个字段为下标获取对应的值,但第一次数组a还没有元素,那么a[$1]是空值,此时数组存储是192.168.1.1=httpd,再遇到192.168.1.1时,a[$1]通过第一字段下标获得上次数组的httpd,把当前处理的行第二个字段放到上一次同下标的值后面,作为下标192.168.1.1的新值。此时数组存储是192.168.1.1=httpd
tomcat。每次遇到相同的下标(第一个字段)就会获取上次这个下标对应的值与当前字段并作为此下标的新值。

4)将第一列合并到一行

说明:

for循环是遍历每行的字段,NF等于3,循环3次。

读取第一行时:

第一个字段:a[1]=a[1]1" " 值a[1]还未定义数组,下标也获取不到对应的值,所以为空,因此a[1]=1 。

第二个字段:a[2]=a[2]2" " 值a[2]数组a已经定义,但没有2这个下标,也获取不到对应的值,为空,因此a[2]=2 。

第三个字段:a[3]=a[3]3" " 值a[2]与上面一样,为空,a[3]=3 。

读取第二行时:

第一个字段:a[1]=a[1]4" " 值a[2]获取数组a的2为下标对应的值,上面已经有这个下标了,对应的值是1,因此a[1]=1 4

第二个字段:a[2]=a[2]5" " 同上,a[2]=2 5

第三个字段:a[3]=a[3]6" " 同上,a[2]=3 6

读取第三行时处理方式同上,数组最后还是三个下标,分别是1=1 4 7,2=2 5 8,3=36 9。最后for循环输出所有下标值。

5)字符串拆分,统计出现的次数

字符串拆分:

统计字符串中每个字母出现的次数:

5)费用统计

6)获取数字字段最大值

7)去除第一行和最后一行

读取第一行,NR=1,不执行print s,s=1

读取第二行,NR=2,不执行print s,s=2 (大于为真)

读取第三行,NR=3,执行print s,此时s是上一次p赋值内容2,s=3

最后一行,执行print s,打印倒数第二行,s=最后一行

获取Nginx负载均衡配置端IP和端口:

读取第一行,i初始值为0,0>1为假,不执行print s,x=example-servers1,i=1

读取第二行,i=1,1>1为假,不执行prints,s=127.0.0.1:80,i=2

读取第三行,i=2,2>1为真,执行prints,此时s是上一次s赋值内容127.0.0.1:80,i=3

最后一行,执行print s,打印倒数第二行,s=最后一行。

这种方式与上面一样,只是用i++作为计数器。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: