- A+
本博文参考的资料来自于骏马金龙的awk教程,该教程在51CTO上也有对应的课程,欢迎大家付费支持。本博文默认读者已经具备了正则表达式基础。
前言
本博客中使用的示例文件a.txt内容如下。
ID name gender age email phone 1 Bob male 28 abc@qq.com 18023394012 2 Alice female 24 def@gmail.com 18084925203 3 Tony male 21 aaa@163.com 17048792503 4 Kevin male 21 bbb@189.com 17023929033 5 Alex male 18 ccc@xyz.com 18185904230 6 Andy female 22 ddd@139.com 18923902352 7 Jerry female 25 exdsa@189.com 18785234906 8 Peter male 20 bax@qq.com 17729348758 9 Steven female 23 bc@sohu.com 15947893212 10 Bruce female 27 bcbd@139.com 13942943905
读取数据的方式
在讲解文本处理工具awk之前我们需要先来看一下命令/工具从文件中读取数据的几种方式,大多数方式我们可以使用bash中的read命令来模拟,无法模拟的我们知道其思路也可以。
1、按字符数量读取。每次根据指定的字符数量来读取数据。
这里注意不要使用要使用-N而不是-n,这样子才可以将空白字符、换行符等也读入。在echo时使用双引号包裹变量引用可以防止空格压缩,前后使用3个中划线“-”用来显示变量头尾,可以看出变量是否包含空白字符。
[root@c7-server ~]# read -N 4 char < a.txt [root@c7-server ~]# echo "---$char---" ---ID ---
2、按字节数量(即数据大小)读取。每次根据指定的字节数量来读取数据。
3、按分隔符读取。从文件开始处读取数据,遇到分隔符后停止,将读取到的数据保存,下次读取时从分隔符位置后继续读取,每次保存的数据中不会包含分隔符本身。
输出结果的第3和第4行数据属于同一次读取的数据,之所以换行是因为数据本身包含了换行符。
[root@c7-server ~]# while read -d "m" chars; do echo "---$chars---"; done < a.txt ---ID na--- ---e gender age e--- ---ail phone 1 Bob--- ---ale 28 abc@qq.co---
... ...
4、按行读取。它可以理解为特殊的按分隔符读取,即分隔符为换行符(n)。
[root@c7-server ~]# while read line; do echo "---$line---"; done < a.txt ---ID name gender age email phone--- ---1 Bob male 28 abc@qq.com 18023394012--- ---2 Alice female 24 def@gmail.com 18084925203--- ... ...
5、一次性读取整个所有数据。它既可以理解为按无限大字符/字节数量读取,也可以理解为按文件中不存在的字符作为分隔符读取。
[root@c7-server ~]# read -N 10000 chars < a.txt [root@c7-server ~]# echo "---$chars---" ---ID name gender age email phone 1 Bob male 28 abc@qq.com 18023394012 ... ... 9 Steven female 23 bc@sohu.com 15947893212 10 Bruce female 27 bcbd@139.com 13942943905 --- [root@c7-server ~]# read -d "_" chars < a.txt [root@c7-server ~]# echo "---$chars---" ---ID name gender age email phone 1 Bob male 28 abc@qq.com 18023394012 ... ... 9 Steven female 23 bc@sohu.com 15947893212 10 Bruce female 27 bcbd@139.com 13942943905---
用法入门
awk 'awk_program' a.txt b.txt a.txt
a.txt:awk命令所要读取的文件,文件可以一个或者多个,也可以重复。文件数也可以是0个,即从标准输入中获取数据。awk并不会将操作直接作用于文件,仅仅是读取文件数据对其进行操作,不会修改源文件。
'awk_program':单引号包裹的内容叫做awk代码/命令/程序。awk看似是一个命令,实则是一门编程语言,因此将其命令称之为代码也是合理的。
awk代码既可以使用单引号包裹,又可以使用双引号,但是在几乎所有情况下都使用单引号。因为在代码中会使用一些诸如“$”这类特殊符号,它既可以被bash解释又可以被awk解释,如果使用双引号那么它们就会被bash解释了,为了将它保留给awk解释所以我们才要使用单引号。
awk代码中常可见“{...}”符号,这个表示的是代码/语句块,块和块之间使用{}分隔,在CLI中同一个语句块内部的多个语句使用分号“;”分隔。如果将代码写在文件中,在CLI中使用-f选项调用的话,则不需要使用分号“;”分隔块内语句。
接下来我们来看2个示例。
[root@c7-server ~]# awk '{print $0}' a.txt ID name gender age email phone 1 Bob male 28 abc@qq.com 18023394012 ... ... 10 Bruce female 27 bcbd@139.com 13942943905 [root@c7-server ~]# awk '{print $0}{print "Hello";print "world."}' a.txt ID name gender age email phone Hello world. 1 Bob male 28 abc@qq.com 18023394012 Hello world. ... ... 10 Bruce female 27 bcbd@139.com 13942943905 Hello world.
在默认情况下,awk按行读取数据,每读取一行就会将整行的内容保存至$0。然后对每一行的数据都执行一次{}代码块中的指令,print指令似于bash的echo命令。因此:
- 第1条命令的含义:对每一行数据执行print $0指令,即打印每行数据。
- 第2条命令的含义:对每一行数据执行两个代码块的指令,第一个代码块等同于第1条,第二个代码块有条指令,第一条打印Hello,第二条打印world.。
BEGIN和END代码块
上文提到的代码块,是在每次awk读入一行(默认)数据时需要执行的操作,这类代码块我们可以将其称之为main代码块,它们是awk的主体(main)代码。
除了main代码块以外,awk还支持两种代码块:BEGIN和END。
awk 'BEGIN{print "I am in the front."}{print "Hello world!"}' a.txt awk '{print "Hello world!"}END{print "I am in the back."}' a.txt awk 'BEGIN{print "I am in the front."}{print "Hello world!"}END{print "I am in the back."}' a.txt
萌新建议将上面的命令敲出来看结果感受一下。这次main代码块中我们没有打印整行数据本身(print $0),而是打印与读入数据无关的信息,来证实main代码块的工作机制。
BEGIN代码块:在读入数据之前就会执行的指令,且仅执行1次。由于这个特性的存在,BEGIN代码块可以不需要读入数据(文件或者STDIN),我们在测试某些awk特性的时候也可以仅使用BEGIN代码块。
~]# awk 'BEGIN{print "I need no files and input."}' I need no files and input.
由于是在读入数据之前就会执行,因此在BEGIN代码块中无法正常引用$0变量,和该变量类似的其他赋值方式相同的变量也无法正常引用。强行引用的话会是空字符串。因为这类变量的赋值均需要在有读入数据的情况下。
END代码块:在所有数据读取并执行完main代码块之后会执行的指令,且仅执行1次。如果有END代码块,那么就必须有读入数据,若没有则必须在STDIN键入Ctrl+d来表示EOF,否则awk命令会阻塞直到遇到EOF。
[root@c7-server ~]# awk 'END{print "aaaa"}' 1 2 3 aaaa
上面的输出结果中,1、2和3是我键入的STDIN,由于没有main代码块因此什么也没有发生(无论是表面上来看还是awk运行内部)。而aaaa则是我键入Ctrl+d后,awk收到EOF,此时awk才执行END代码块的内容。
END代码块中可以引用$0类的变量,其值与读入数据最后一行(默认)的数据相关。
[root@c7-server ~]# tail -n 1 a.txt 10 Bruce female 27 bcbd@139.com 13942943905 [root@c7-server ~]# awk 'END{print $0}' a.txt 10 Bruce female 27 bcbd@139.com 13942943905
一般来说,为了看起来更舒服,我们一般将BEGIN写在main之前(左侧),END写在main之后(右侧),中间有一个或者多个main。
代码块中的指令可以为空(即没有,啥也不干)。main代码块为空的话则可以连大括号都省略了。
awk 'BEGIN{}{}{}{}END{}' .... awk 'BEGIN{}END{}' ....
更新awk
awk这个工具最早是在1977年由3位大牛所编写,工具名称来源于三位程序员的名字:Alfred Aho、Peter Weinberger和Brian Kernighan。而我们现在在Linux上所使用的awk则是来源于GNU组织所改变的gawk,为了不改变大家的使用习惯,使用awk做了软链接。
~]# ls -l $(which awk) lrwxrwxrwx 1 root root 30 Dec 13 17:32 /usr/bin/awk -> gawk
程序包名称为gawk。
~]# rpm -qa | grep "awk" gawk-4.0.2-4.el7_3.1.x86_64
教程作者使用的版本是4.2.0,因此为了后续实验的正确性我们需要使用4.2.0版本的awk。作者给出了更新方式。
wget --no-check-certificate https://mirrors.tuna.tsinghua.edu.cn/gnu/gawk/gawk-4.2.0.tar.gz tar xf gawk-4.2.0.tar.gz cd gawk-4.2.0/ ./configure --prefix=/usr/local/gawk4.2 && make && make install ln -fs /usr/local/gawk4.2/bin/gawk /usr/bin/awk awk --version gawk --version
当我们想使用4.2.0版本的awk的时候直接键入awk命令,想使用4.0.2旧版本的awk时则键入gawk命令。在我撰写本博客的时候,GNU awk官方的documentation已经更新到5.1的了。