Linux文本处理三剑客之awk学习笔记01:前言

  • A+
所属分类:linux技术
摘要

本博文参考的资料来自于骏马金龙的awk教程,该教程在51CTO上也有对应的课程,欢迎大家付费支持。本博文默认读者已经具备了正则表达式基础。


本博文参考的资料来自于骏马金龙的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的了。