Linux文本处理三剑客之awk(精简版哦!)

一、awk是什么?

  awk由其三位创作者 Alfred Aho,Peter Weinberger, 和 Brian Kernighan 的 Family Name 的首字符组成的,与之前说过的流式文本编辑器sed(sed篇在这儿)虽都能匹配处理文本,但awk是一种编程语言。解释定义是我所不擅长的,但是如果是一种编程语言,毋庸置疑就会有一定的语法。在这里主要与大家一同学习如何去使用它!

二、工作原理

  awk在处理一段文本时,首先会逐行扫描并处理文件,从第一行到最后一行,寻找匹配的特定模式的行,并在这些行上进行指定的操作。如果没有指定处理动作,则会把匹配的行显示到标准输出(屏幕);如果没有指定匹配模式,则所有行都会被处理。

三、如何使用?

1、命令语法

  awk [options] ‘program’ filenames

  其中,program表示一段代码,一般由多个pattern和action组成,当读入的记录被匹配到时,才会执行相应的动作。
  /pattern/和{action}需要用单引号引起来。

2、两个概念

  ① 记录:文本文件的每一行代表一个记录,$0可以表示当前记录的所有字段,也就是整条记录;记录分隔符默认为换行符,换行符可以通过RS和ORS两个内置变量来指定。
  ② 字段:文本文件的每一列代表一个字段,$n表示当前记录的第n个字段;字段分隔符默认为空格,换行符可以通过FS和OFS两个内置变量来指定。

在这里插入图片描述
  举个小栗子哦!先给出一段测试文本:


001 zhangsan:2000
002 lisi           :2300
003 wangwu  :3100
004 zhaoliu    :2900
005 wuqi        :3400


[root@service ~]# awk '{print $0}' testfile    //打印整条记录
001 zhangsan:2000
002 lisi    :2300
003 wangwu  :3100
004 zhaoliu :2900
005 wuqi    :3400
[root@service ~]# awk '{print $1,$3}' testfile    //打印第1个和第3个字段,以空格为字段分隔符那么第一条记录只有两个字段
001
002 :2300
003 :3100
004 :2900
005 :3400

3、匹配模式

  前面说过,awk会逐行扫描,寻找匹配的特定模式的行,可以将其理解为"条件",满足则处理。那么模式都支持什么形式呢?

条件类型 条件 解释
正则表达式 /正则/ 默认支持扩展正则(如果你不会,click here!),"//“中也可以写入字符,但是不要忘记”//"哦!
行模式范围 /正则1/,/正则2/ 从被从被正则1匹配到的行开始,到被正则2匹配到的行结束
关系表达式 >、<、>=、<=、==、!= 尤其注意"==“判断两个值是否相等,而”="用来给变量赋值
模式匹配表达式 x~/正则/、x!~/正则/ A~B、A!~B分别判断x是否与指定的正则相匹配,前者匹配则为真,后者反之
保留字 BEGIN 在 awk 程序处理文本之前需要执行的操作,即刚开始时执行。BEGIN 后的动作只在程序开始时执行一次
保留字 END 在 awk 程序处理完所有行之后所需要执行的操作,即将结束时执行。END 后的动作只在程序结束时执行一次

  下面主要学习一下BEGIN和END这两个模式。
  BEGIN:指定了处理文本之前需要执行的操作,一般用来进行一些初始化操作,比如定义全局变量、打印一些头部信息。可以有一个或多个awk命令,该模式是可选的。
  END:指定了处理完所有行之后所需要执行的操作,一般用来打印一些结尾信息,或做一些清理动作。可以有一个或多个awk命令,该模式是可选的。

在这里插入图片描述
  通过这两个模式,就可以为上面说过的测试文件加上表头和一些提示信息。

[root@service ~]# awk 'BEGIN{print"ID  name     salary"} {print $0} END{print"表中数据纯属瞎编(∩_∩)"}' testfile
ID  name     salary
001 zhangsan:2000
002 lisi    :2300
003 wangwu  :3100
004 zhaoliu :2900
005 wuqi    :3400
表中数据纯属瞎编(∩_∩)

4、常用选项

选 项 解 释
-f<scriptfile> 执行指定的awk脚本文件来处理文本
-F<Symbol> 定义字段分隔符
-v<var=value> 执行程序之前定义变量

  还是上面的测试文件,举个小栗子哦!

#执行指定的awk脚本文件来处理文本
[root@service ~]# cat awk_script    //awk脚本写到awk_script文件中
BEGIN{print"ID  name     salary"}
{print $0}
END{print"表中数据纯属瞎编(∩_∩)"}
[root@service ~]# awk -f awk_script testfile    //执行效果与前面一样哦
ID  name     salary
001 zhangsan:2000
002 lisi    :2300
003 wangwu  :3100
004 zhaoliu :2900
005 wuqi    :3400
表中数据纯属瞎编(∩_∩)

#定义字段分隔符 
[root@service ~]# awk -F":" '{print $1}'  testfile    //定义":"为字段分隔符
001 zhangsan
002 lisi
003 wangwu
004 zhaoliu
005 wuqi

#定义变量
[root@service ~]# awk -v "a=1" '{print $a}'  testfile    //定义变量a=1,并打印$a至屏幕
001
002
003
004
005

5、内置变量

  前面提到了RS、ORS、FS、OFS这些内置变量,其实awk还提供了很多其他的内置变量,在这里我们对他们进行了解学习。

选 项 解 释
$n 当前记录的第n个字段
$0 当前记录的全部字段
NF 当前输入记录中字段的个数
NR 已读记录的个数
FNR 与NR类似,但awk如果打开一个新文件,FNR便会重新计数
FS 字段分隔符,默认为空格
OFS 输出字段的分隔符,默认为空格
RS 记录的分隔符,默认为换行符
ORS 输出记录的分隔符,默认为换行符
RT 记录终止符
RSTART 被匹配子串的起始索引值
RLENGTH 被匹配子串的长度
ARGC 命令行参数的个数
ARGV 存放命令行参数的数组
ENVIRON 存放当前环境变量的数组
FILENAME 存放当前输入文件的名称

  只看概念肯定马马虎虎,重点还是通过以下栗子来理解吧!对之前的测试文件进行一点修改:


001 zhangsan:2000 abc
002 lisi           :2300 def
003 wangwu  :3100 ghi
004 zhaoliu    :2900 jkl
005 wuqi        :3400 mno


  ① NF and NR:

[root@service ~]# awk '{print NR,NF}'  testfile
1 3    //已读1条记录,第一条记录中有3个字段
2 4    //已读2条记录,第二条记录中有4个字段
3 4    //...
4 4    //...
5 4    //...

#可以利用NR打印行号哦!
[root@service ~]# awk '{print NR,$0}'  testfile
1 001 zhangsan:2000 abc
2 002 lisi    :2300 def
3 003 wangwu  :3100 ghi
4 004 zhaoliu :2900 jkl
5 005 wuqi    :3400 mno

  ② FNR:

#在处理多个文件时,就可以看出NR与FNR之间的区别啦!
[root@service ~]# cp testfile testfile_copy    //所以我们copy一份测试文件
[root@service ~]# awk '{print NR,FNR,$0}'  testfile testfile_copy
1 1 001 zhangsan:2000 abc
2 2 002 lisi    :2300 def
3 3 003 wangwu  :3100 ghi
4 4 004 zhaoliu :2900 jkl
5 5 005 wuqi    :3400 mno
6 1 001 zhangsan:2000 abc    //可以看到,在处理第二个文件时,FNR重置后继续计数哦!
7 2 002 lisi    :2300 def
8 3 003 wangwu  :3100 ghi
9 4 004 zhaoliu :2900 jkl
10 5 005 wuqi    :3400 mno

  ③ FS and OFS:

#变量FS与-F选项的功能相同,都是指定字段分隔符。
#如果不指定,默认的字段分隔符为"空格"。
[root@service ~]# awk 'BEGIN{FS=":"}{print NR,$1}' testfile    //指定 ":"为输入字段分隔符
1 001 zhangsan
2 002 lisi
3 003 wangwu
4 004 zhaoliu
5 005 wuqi
[root@service ~]# awk 'BEGIN{OFS="@_@"}{print NR,$1,$2}' testfile    //指定 "@_@"为输出字段分隔符,其中NR变量所输出的行号也算一个字段哦!
1@_@001@_@zhangsan:2000
2@_@002@_@lisi
3@_@003@_@wangwu
4@_@004@_@zhaoliu
5@_@005@_@wuqi

  ④ RS and ORS:

#理解了上面的FS和OFS,那么RS和ORS就很简单。
#如果不指定,默认的记录分隔符为"换行符"。
[root@service ~]# awk 'BEGIN{RS=":"}{print NR,$0}' testfile    //指定 ":"为输入记录分隔符
1 001 zhangsan
2 2000 abc
002 lisi
3 2300 def
003 wangwu
4 3100 ghi
004 zhaoliu
5 2900 jkl
005 wuqi
6 3400 mno

[root@service ~]# awk 'BEGIN{ORS="@@@@@"}{print $0}' testfile    //指定 "@@@@@"为输出记录分隔符
1 001 zhangsan:2000 abc@@@@@2 002 lisi    :2300 def@@@@@3 003 wangwu  :3100 ghi@@@@@4 004 zhaoliu :2900 jkl@@@@@5 005 wuqi    :3400 mno@@@@@

  ⑤ RT:

#RT解释为记录终止符,如何理解呢?
#其实可以认为他就是分隔符!
[root@service ~]# awk 'BEGIN{RS=":"}{print NR,$0,RT}' testfile
1 001 zhangsan :
2 2000 abc
002 lisi     :
3 2300 def
003 wangwu   :
4 3100 ghi
004 zhaoliu  :
5 2900 jkl
005 wuqi     :
6 3400 mno

  ⑥ RSTART and RLENGTH:

在这里插入代码片

  ⑦ ARGC and ARGV:

[root@service ~]# touch test{1,2}    //创建两个测试文件,分别为test1和test2
[root@service ~]# echo"1" >> test1    //随意添加一条内容到test1中,添加一行内容是为了后续操作有一次输出
[root@service ~]# awk '{print ARGC,ARGV[0]}' test1 test2
3 awk
[root@service ~]# awk '{print ARGC,ARGV[1]}' test1 test2
3 test1
[root@service ~]# awk '{print ARGC,ARGV[2]}' test1 test2
3 test2
#ARGV这个数组存放了awk、test1和test2三个参数,可以看到'program'部分并不作为参数
#ARGC那就更好理解了,它表示参数的数量,也可以理解为ARGV数组的长度

  ⑧ ENVIRON:

#ENVIRON数组存放了一些系统环境变量
[root@service ~]# awk  'BEGIN{for (i in ENVIRON) {print i"="ENVIRON[i];}}'
AWKPATH=.:/usr/share/awk
SELINUX_LEVEL_REQUESTED=
SELINUX_ROLE_REQUESTED=
LANG=zh_CN.UTF-8
HISTSIZE=1000
XDG_RUNTIME_DIR=/run/user/0
USER=root
_=/usr/bin/awk
SELINUX_USE_CURRENT_RANGE=
...省略部分内容

  ⑨ FILENAME:

#很容易理解,就是存放输入文件名称的变量。但是如果有多个文件,存放的是被匹配的那个。
[root@service ~]# touch test{1,2}    //创建两个测试文件,分别为test1和test2
[root@service ~]# echo 1 >  test4 && echo 1 > test5    //随意添加一条内容到test1和test2中,为的是可以进行匹配
#两个文件中都只有一条内容
[root@service ~]# awk 'NR==1{print FILENAME}' test1 test2    //NR=1匹配的是第一条记录
test1
[root@service ~]# awk 'NR==2{print FILENAME}' test1 test2    //NR=2匹配的是第二条记录
test2

6、格式化输出

  相信很多学习过C语言的同学都对他很熟悉,那就是printf函数。在awk中,print动作与我们所学习过的printf函数大同小异,下面我们一起来了解一下语法格式吧!


  其中,类型符的数量必须与传入的参数的数量相同,也就是说,类型符和传入的参数必须一一对应。至于具体的参数项,这里不做过多介绍,后面会在总结Shell中展示。

7、流程控制

  ① 条件语句;

  ② 循环语句;

  ③ 控制转移语句


  在条件判断中当然"与或非"必不可少,分别是:&&、||、!,这里不做过多介绍。

8、关联数组

  什么是关联数组?相信你一定会有疑问。关联数组通俗理解,就是不但可以通过整数进行索引,还可以通过字符串或其他类型的值来索引它。这样做有什么便捷之处呢?可以接着看下面的栗子。

#统计一段文本文件中每个单词出现的次数
[root@service ~]# cat testfile
cat like fish,dog like bone.
[root@service ~]# awk -F"[,. ]" '{for(i=1;i<NF;i++){count[$i]++}}END{for(i in count){print i,count[i]}}' testfile|sort -t ' ' -k 2r
like 2
bone 1
cat 1
dog 1
fish 1

  看到了吗,利用数组的索引唯一性,可以达到一个去重的效果。上例中," for(i=1;i<NF;i++){count[$i]++} " 的含义为:遍历每一行的所有字段,将每个字段的内容作为count数组的下标(索引),如果匹配到该索引,那么就给count数组的该索引所在的值+1(初值为0)。

  如果想要删除数组中的某一个元素或整个数组,可以使用delete函数。比如我们想删除count数组中的"cat"索引的值:delete count["cat"];,或者删除整个count数组:delete count;

9、内置函数

  ① sub函数;

  sub函数会用替换字符串替换正则匹配字符串。如果没有指定替换范围,默认匹配整个记录;并且替换只发生在第一次匹配的时候。

  语法格式:sub(正则表达式,替换字符串[,替换范围])

  举个栗子

[root@service ~]# cat testfile
cat like fish,dog like bone.
cat like fish,dog like bone.
[root@service ~]# awk '{sub(/l..e/,"dislike") ;print $0}' testfile    //不指定替换范围,每一行第一次匹配到的内容会被替换
cat dislike fish,dog like bone.
cat dislike fish,dog like bone.
[root@service ~]# awk  '{sub(/l..e/,"dislike",$4);print $0}' testfile    //指定替换范围后,替换只发生在该区域内
cat like fish,dog dislike bone.
cat like fish,dog dislike bone.

  ② gsub函数

  gsub函数与sub函数作用相似,区别在于gsub是全局替换,如果不指定替换范围,默认匹配整个记录;并且替换每一个符合条件的字符串。

  语法格式:gsub(正则表达式,替换字符串[,替换范围])

  举个栗子

[root@service ~]# cat testfile
cat like fish,dog like bone.
cat like fish,dog like bone.
[root@service ~]# awk  '{gsub(/l..e/,"dislike");print $0}' testfile    //不指定替换范围,每一行每一次匹配到的内容都会被替换
cat dislike fish,dog dislike bone.
cat dislike fish,dog dislike bone.
[root@service ~]# awk  '{gsub(/l..e/,"dislike",$4);print $0}' testfile    //指定替换范围,替换只发生在该区域内
cat like fish,dog dislike bone.
cat like fish,dog dislike bone.

  ③ match函数
  match函数用来返回子串在原始字符串中的位置的索引,如果没有匹配到则返回0。前面说过的内置变量RSTART和RLENGTH分别存放字符串中被匹配子字符串的开始位置和长度。

  语法格式:match(原始字符串,字串)  注:子串位置可以使用正则表达式

  举个栗子

#测试文件虽然与本例无关,但是其中需只有一行内容,这样才能会有内容的输出
[root@service ~]# awk  'BEGIN{s="abcdefg";r="cdef"}{match(s,r);print RSTART,RLENGTH}' testfile    //字符串赋值给变量,通过调用变量实现  
3 4
[root@service ~]# awk  '{match("abcdefg","cdef");print RSTART,RLENGTH}' testfile    //直接使用字符串
3 4
[root@service ~]# awk  '{match("abcdefg",/c.../);print RSTART,RLENGTH}' testfile    //使用正则匹配
3 4

四、结束语

  本篇没有列举太多的实例,主要还是总结了一些awk中的知识点便于复习使用,后续如果有时间会继续扩展添加内容,谢谢支持哦!(๑′ᴗ‵๑)

猜你喜欢

转载自blog.csdn.net/weixin_43898125/article/details/107471472