使用linux系统的人或许都听说过linux三剑客,它们分别是awk、sed和grep,在一切皆文件的linux系统中,这三种文本处理工具的使用显得非常重要。今天就主要介绍三剑客之一的awk,使用awk的优势很多,很方便快速,功能非常之强大,其也是Linux及Unix环境中现有的功能最强大的数据处理引擎之一。
为什么使用awk
awk作为linux中强大的文本处理工具,实际上也是一门编程语言了。因为有awk的存在,一些较为复杂的数据清洗及数据筛选的工作可以以很短的命令轻易地被解决而不需要每次都写一个脚本,正因为其在命令行运行的特点也使其能够通过管道与Shell脚本很好的交互,这是其他编程语言所不能及的(也正是因此听说在很多运维的面试中关于awk的问题的出现几率非常高)。
另外一个特点是awk读取文件时默认按一条条的记录进行读取的(默认就是一行一行地读取,但是awk是可以设定记录分隔符从而不仅仅是按行读取记录的),而在某些编程软件比如python中,若是使用pandas进行读取文件需要先将整个文件载入到内存,这样就十分地占据内存,同时在进行操作时也相对更费时间。但若是在awk中由于其按行操作,若是匹配到目标字符时就不再向后读取文件,相对更加节省内存,也更符合某些工作需求。
当然awk不是万能的,若是将非常复杂的文本处理工作交给awk可能就有点不划算了。
awk的使用方式
使用awk的命令一般为两种,即:
# 1. awk 'pattern {action}' filename
# 2. awk '{pattern + action}' filename
其中第一种使用方式最常用,也更与一般的问题解决方式契合(即先设定条件或“模式”,再对符合条件的目标执行“动作”),而第二种将“模式”和“动作”放在一起,可读性较差同时还可能导致代码混乱。任意的使用方式,只要能得到结果即可,在今天的文章中也只介绍第一种。接下来列出其中的一些要点:
- pattern代表的是筛选要使用的模式,也可以理解成筛选条件,符合条件的才被执行后面的action中的命令。
+ pattern中能支持的筛选方式有正则表达式、比较计算、逻辑与(pattern && pattern)、逻辑或(pattern || pattern)、逻辑取反(!pattern)、改变优先级(如使用括号)等 - 使用单引号将pattern和action两部分包围代表这是一个部分不可分割,awk识别这个部分中的模式和命令,并对要操作的文件进行相应的处理。这里最好只使用单引号,因为双引号有时候会出问题,文章后面会解释。
- action即对符合pattern筛选的记录进行某些动作,可以是linux中的任何语句,但常用的命令有print、split、substr、sub、gsub等。
- action中使用多个语句需要使用分号分割(与shell中相同)。
- pattern和action在某些情况下可以省略。
- 一个awk命令可以包含多次pattern{action},直接连用即可。
- 若没有要读取的文件,那么需要通过管道命令将要处理的数据交给awk进行处理,如在服务器中对某些记录(如ifconfig)的处理。
- awk中支持流程控制语句,如while、if、do/while、for、break、continue等等,可以将shell命令和其联动,因此其可以写出很复杂的语句,这种较复杂的语句一般放在脚本中被执行。
除了上面的要点外,awk也可以接一些参数,接下来介绍两种最常用的参数:
- -f 有时候若是awk命令要执行的代码较多,写在命令行上会很混乱,这时将这些代码写在一个脚本中,使用-f参数表示要执行的pattern和action都来自-f后接的文件。
- -F 此参数指定用于awk分隔字符的字符,awk默认的分隔字符为空格或者tab键,比如若是csv文件,那么你可以设定-F 为
,
。
接下来展示几个例子:
使用比较进行筛选
cat /etc/passwd |awk -F ':' '$1=="root" {print $7}'
# 使用上面的命令查看root用户默认使用的shell
# 运行结果如下图,可以直接拷贝到shell命令行中去试试哦,得到的结果应该是一样的。
可以看到root用户默认使用的shell是bash。分析一下上面的例子:
- 首先使用cat命令查看passwd文件,并使用管道命令将结果传递给awk进行处理。
- 由于passwd中的信息都是以冒号
:
分割的,和默认分隔符不同,因此需要制定要使用的分隔符为:
- 刚刚提到了awk是逐个读取每条记录的,默认情况也就是逐行读取的,对于每一行中的内容都使用分隔符分隔,这样得到了一个类似列表的结构,使用$符号加上数字就表示要取出列表中的这个数字对应下标的字符串,注意$0代表的是整行。
- 后面的单引号中的内容就是一个标准的pattern{action}模式,前面pattern中的$1=="root"表示需要筛选的是被分隔符分隔得到的列表中的第一个字符串为"root"的行,只有第一个字符串为"root"才会被筛选。
- 这里解释一下刚刚提到的为什么不能使用双引号在pattern{action}外面,这是因为在linux中双引号包含的内容会被linux shell获取,从而导致$1这样的变量被shell误解为是shell自身的变量而不是awk中的变量,只有使用单引号才不会导致这样的问题。
- 通过筛选的行被传给action,这里的action对行进行处理,在这里使用print打印出第七个部分,也就是root所使用的shell。执行结果与预期相同。
当然数值比较也是一样的,这里就不展示例子了。
使用正则表达式进行筛选
正则表达式这里就不多介绍了,其对于编程的重要性不言而喻,linux中的正则表达式也是很重要的,接下来使用正则表达式达到上面例子的效果:
cat /etc/passwd |awk -F ':' '/root/ {print $7}'
# 其实也非常的简单,将要匹配的正则表达式使用两个斜杠包裹起来就行了,这里匹配含有root字符的记录
# 结果也是/bin/bash
逻辑符号
cat /etc/passwd |awk -F ':' '/home/&&$7=="/bin/bash" {print $1}'
# 这里的&&表示前后的两个条件都要符合才行,即含有home目录且默认shell为bash的用户名(第一列)
# 运行结果是很多人名,就不展示出来了,读者可以自己拷贝过去试试
awk语句中的BEGIN和END
上面提到过awk中可以有多个pattern{action},只需要直接连用即可,这里的BEGIN和END就是pattern的一种特殊形式,BEGIN携带的action是在开始处理文本数据之前先要执行的,而END携带的action则是在处理文本数据结束后才执行的,因此中间的处理部分可以被称为main部分,main部分也可以有多项。不要小看这两条语句,实际应用中是十分有用处的。
先看一个简单的例子吧,比如想要看使用bash作为默认shell的用户为哪些,那么可以使用如下的命令:
cat /etc/passwd | awk -F ':' 'BEGIN {print "开始咯"} $7=="/bin/bash" {print $1} END {print "结束咯"}'
# 例子可以直接拷贝到shell命令行运行试试
# 运行结果
中间有很多用户名,考虑隐私问题不能展示。不过可以看到运行的结果中首先打印出开始,最后再以打印结束而结束,中间的条目是由$7=="/bin/bash" {print $1}
所产生的,也就是说BEGIN和END部分都只执行一次,而内部的语句则一次读取一条记录直到整个文件信息读完,这个点需要注意!
接下来展示一个比较有用的例子,即对ifconfig内容的处理,ifconfig命令是用于显示或设置网络设备的命令,默认情况下使用ifconfig命令得到的结果为很长的几段,并且段与段间有一个空行,如下图所示:
若是想要取得其中ipv4的地址,也就是inet后的内容。接下来分析一下处理过程:
- 联系到上面提到的记录分隔符,若是将一个段落作为一条记录的读取那么将会比较方便。
- 那这时候我只需要将分隔符设置为两个换行符(
\n\n
)即可,如上图所示。 - 设置分隔符最好在BEGIN中设置,因为在BEGIN中设置的选项会在一开始就被awk获取到,若是在main部分才设置,会先读取一条记录后才获取到设置的分隔符,这个读者感兴趣可以试试。
- 经过计算后,发现地址在每个段落中的第六个位置(即$6)。
分析结束后写出的代码:
ifconfig |awk 'BEGIN {RS="\n\n"} {print $6}'
# 这里的RS是awk的一种内置变量,意思是记录分隔符。awk中的内置变量有十多个,可以直接使用
# 后面会列出awk中的内置变量
得到的结果如下图:
若是想要将结果都打印在一行中以tab符号分隔,可以再加一个内置变量即输出分隔符(OFS),默认的输出分隔符是换行符,所以我们看到的每条结果都是以换行符隔开的,使用OFS可以自定义分隔符:
ifconfig |awk 'BEGIN {RS="\n\n";ORS="\t"} {print $6} END {print "\n"}'
# 每个命令间以分号分隔即可
# 这里最后输出结束没有换行符,所以在END处多加了一个换行符
执行文件中的awk命令
若是执行较短的命令是没有必要将命令写到文件中的,但若是命令很长,逻辑也较为复杂,那么写在脚本中则会比较清晰明了且不易出错,这里我将上面的例子写到文件中做一个展示:
文件内容:
运行结果同上。
awk中的内置变量
图源:https://www.runoob.com/linux/linux-comm-awk.html
其中$、FS、NR、RS等较为常用,其它的变量在需要使用时可以再去深入理解。
叮
awk的功能很强大,它可以完成很多工作,写成脚本也可以写的很复杂,有兴趣的读者可以去看一下骏马金龙
这位博主的18个awk的经典实战案例
,其中的案例相对较复杂,将awk能完成的较经典的案例都讲了一遍,并且讲解十分清晰,值得一看。分别有博客和视频两版。
参考:https://www.bilibili.com/video/BV1BJ411X7QN?p=14
参考:https://www.runoob.com/linux/linux-comm-awk.html