一、综述
1.1 版本和历史
目前大多数Linux发布版对于awk的支持,都提供gawk(awk的GNU实现)或者mawk(awk的一个快速、删减版),甚至nawk。
想要下载gawk,在Ubuntu下可以直接sudo apt-get install gawk
1.2 gawk工作原理
首先:gawk命令的书写模式:
gawk [options] [program] [file-list]
例如:
gawk '/^chen/{print $0}' file #进行正则表达式行首chen字符串匹配(pattern匹配),若匹配成功则打印整个记录(若pattern匹配成功,则执行action)。
说明:
[options]是一些特殊选项,一般都没有什么用,就自动省略了 :)
[program]是gawk工具的精髓。它由模式(pattern)和动作(action)组成。当然任意一方都可以缺省,也可以有多个模式和动作、其中pattern匹配可以是正则表达式和条件表达式;action中有控制语句、变量、运算符等等,可以做统计。
[file-list]是一些想要处理的文件,中间用空格依次排开
gawk从file中,一次会选择一行的内容作为输入。
在默认输入记录分隔符为换行符的情况下,一行就是一个记录。
在默认输入字段分隔符为空格和制表符的情况下,一个记录被分解成众多字段,存储在已编号的变量中,从$1~$n,最多100个。
然后依次进行pattern匹配,在匹配成功后进行action处理。
若有多个pattern,则类似于C语言中的多条语句,前一个pattern和action处理完后,依次再进行下一个pattern匹配。
在这一个记录处理完后,再读取下一行,依次重复上面的操作。
1.3 awk运用场景
awk和grep都是文本处理工具,都有文本搜索功能,那么awk相较于grep,可以运用于那些场景,有什么优势?
例子:
① 提取apache的日志,统计访问最多的ip,或者统计ssh连接的最多ip时
② 一份记录全班成绩的文件,只想提取出里面数学成绩时
姓名 | 数学 | 语文 | 英语 | 地理 | 科学 |
说明:
第一种,是很常见的awk用于统计的情况,由于awk有变量,数组和控制语句,所以非常适合于做统计
第二种,是很常见的按列提取数据的情况。awk的字段,可以把文本按列的方式显示出来
上面这些都是grep做不到的;grep做不到统计,更做不到分列显示。点击打开链接
二、[program]讲解
2.1 [program]组成
① 模式(pattern):从输入行中选取文本行
② 动作(action):如果pattern匹配成功,就执行指定的action。
问:为什么这部分叫program呢?
因为这一部分确实是在写代码,不同的是,这些代码好像没有我们在C语言下面看到的那样完整:有开始,有结束的。
之所以这样,是因为这个框架已经被内置了,我们写的这部分代码,确实是代码,不过是在awk代码框架中添加的我们想要执行的一部分代码。
这样做的好处就是容量可以更加精简。并且awk语言也是一种解释型语言,不需要经过编译连接这样的过程。
2.2 [program]部分,标准格式
格式:
‘/pattern/{action}’
注:
① 整个[program]部分,用单引号括起来,表示将这部分提交给gawk命令进行执行。
② pattern可以缺省,即写为’{action}’。pattern缺省之后,表示对输入的每一行都执行动作。
③ action也可以缺省,即写为’/pattern/’。action缺省后,表示对匹配的模式执行默认的动作,即print。
④ gawk是怎样支持基本正则表达式和扩展正则表达式的?正则表达式介绍,而这一部分就是通过模式pattern来显示的。
在匹配时,pattern可以是非完全表达,通过正则表达式来匹配想要的内容。
⑤ awk语言基本概念和模式说明
awk是一门语言,它具有一个完整语言所具有的几乎所有特性。并且它有自己的语法、变量、函数、运算符、数组和控制结构,而这些都体现在action中。
类似C语言的main,awk语言也有自己固定的框架,不过这个框架是固定好的,不需要我们再动手去写。
当我们在写action时,实际是在向这个框架中添加一些我们想要的操作。
一个动作,就类似C语言的一条语句,多个动作之间需要用“;”(分号)进行分隔。这和C语言中两条语句之间通过分号分隔是一个概念。
实际上,awk语言中很多结构都是来自C语言。
⑥ 多个模式,怎样进行组合问题
如果有多个模式,则直接在前一个后面进行添加,如:
‘/pattern1/{action1}/pattern2/{action2}…’
花括号就是用于分隔pattern和action,以及规定一组action用的。
pattern和其后的action是一组,缺省pattern可以写成:
‘/pattern1/{action1}{action2}’
此时action2相应没有pattern,所以就是对所有输入的行都执行action2。
三、[program]的pattern部分
pattern是由正则表达式、判别条件真伪的表达式,或二者的结合构成的。
① 正则表达式的介绍:https://blog.csdn.net/huayangshiboqi/article/details/80514778#t4
② 判别条件真伪的表达式的介绍,则由3.1 布尔运算符和3.4关系运算符这两个部分给出。
形式:
在pattern中,一般正则表达式的形式是:/pattern/,判别条件真伪表达式的形式是:str1 == str2 (“==”符号只是用于特定举例,可以是关系运算符或者是布尔运算符任何一个,前后两个是变量)
awk虽然支持基本和扩展的正则表达式,但并不是所有的元字符它都能够支持,比如下面这些正则表达式中的元字符它就不支持。
表3.1 任何awk版本都不支持的正则表达式 | |
元字符 | 说明 |
\< \> | 单词定位 |
\ ( \) | 向前引用 |
\{ \} | 重复 |
3.1 pattern中的布尔运算符
第⑥条是说要匹配多个模式。
但是在这些模式的书写中,如何最准确的匹配出我们想要的模式?
pattern中不仅可以使用基本和扩展的正则表达式,还可以使用布尔运算符:“||”(或)和“&&”(与),来使匹配的模式更加符合预期。
比如:
awk ‘/a||b/{print 234}’ file
表示匹配a或者b中的一个即可。
gawk ‘$2 >5 && $2< 15’ file
表示如果某记录的第二个字段,值在5-15之间,就打印该记录。
3.2 pattern的特殊模式
在模式中,可以自己去匹配自己想匹配的模式。但是,awk同时规定了两个特殊的模式:BEGIN和END。
BEGIN:是在匹配开始前,执行的一种模式。
END:是在所有匹配结束后,执行的一种模式。
比如:
gawk ‘BEGIN{print hello}’ file
3.3 pattern中的逗号
逗号是范围运算符。
pattern是用来匹配一种模式的。如果有两个pattern,并且中间用逗号进行隔开,则表示:从匹配第一种模式的初始行开始,到匹配到第二种模式为止,这中间所有的文本,都被选中。
比如:
gawk ‘FNR==2 , FNR==5’ file-list
两种模式用逗号进行了隔开。就表示匹配第二行到第五行所有的文本,并执行默认的action,print
(FNR是一个变量,在4.1节中有介绍)
3.4 pattern中的判别条件真伪表达式
判断条件真伪表达式,一般是由关系运算符进行操作的。
表3.2 支持的关系运算符 | |
运算符 | 含义 |
< | 小于 |
<= | 小于等于 |
== | 等于 |
!= | 不等于 |
>= | 大于等于 |
> | 大于 |
示例:
gawk ‘$2 > 2 {print $0}’ file
如果第二个字段的数值大于2,就输出这整条记录。
注:默认,记录是按照换行符来划分的,即一行为一个记录。
但是,输入记录分隔符是可以改变的,可以不按照换行符作为分隔符。表4.1 变量中有说明。
使用正则表达式的pattern举例:
gawk ‘/^marry/{print}’ file
表示匹配行首的marry。
3.5 pattern中的符号“~”和“!~”
作用:“~”符号用于测试某个字段、记录或者变量是否匹配正则表达式。
“!~”则正相反,用于测试某个字段、记录或者变量不匹配正则表达式。
在3.4中,关系运算符主要用于数值;正则表达式则主要用于字符串。
能不能单独对某个字段进行测试?
举例:
gawk ‘$1 ~ /marry/{print}’ file
pattern部分是:$1~/marry/
action部分是:print
pattern部分的意思是:对第一个字段进行marry匹配,如果某记录(行)的第一个字段匹配成功,则打印这整个记录。
四、[program]的action部分
action是完全按照awk的语法来写的一组语句的集合。可以有变量、函数、运算符、控制语句等等。
多个action之间通过“;”(分号)进行分隔。
4.1 变量
表4.1 部分程序变量 | |
变量 | 含义 |
$0 | 当前记录 |
$1 ~ $n | 当前记录($0)中的1~n个字段 |
RS | 输入记录分隔符(默认为换行符) |
FS | 输入字段的分隔符(默认空格或制表符) |
NF | 当前记录的字段数目 |
NR | 当前记录的记录编号 |
OFS | 输出字段分隔符(默认为空格) |
ORS | 输出记录分隔符(默认为换行符) |
FILENAME | 当前输入文件的名称(null表示标准输入) |
FNR | 区别于NR的是,是否为当前文件的记录编号 |
gawk有两种变量:用户变量和程序变量。
不需要在使用变量之前声明它们。不过可以选择把初始值赋予这些变量。
变量即可以在pattern中,也可以使用在action中,尤其是控制语句中,作为条件判断。
例如:打印第n行的数据:
gawk ‘FNR==n{print}’ file-list #变量作为模式
或
gawk ‘{if(FNR==n){print}}’ file-list #把变量运用于控制语句
前一种直接用变量进行pattern匹配,后一种是在action中使用控制循环语句进行筛选。
问1:NR与FNR什么区别
就是当前文件和所有文件的区别。
gawk后面会接file-list。如果是FNR,就是file-number-of-record,只记录当前文件的行数。
如果是NR,number-of-record,记录的就是输入的所有文件file-list的总记录行数。
在条件判断中,if(NR==FNR)是用来判断是否在处理第一个文件。
问2:NF与NR的区别
NF是字段数目;NR是记录数目
字段和记录的区别可以参考1.2节。
这两个变量,在读入数据流时就已经固定了。
问3:NF、NR与$0、$1~$n之间的区别
NF、NR只是用于计数,是数值
$0、$1~$n都是实际从文件中读取的字符串
问4:输入字段分隔符的更改
NF,字段分隔符默认是空格和制表符。由表4.1中的FS变量可进行更改。
例如:
功能:按照“:”(分号)分隔第一行数据流的字段,如果总字段数目大于等于2,则输出第1个字段的字段串
① gawk ‘BEGIN{FS=”:”} NR==1{if(NF>=2) print $1}’ file
gawk -F: ‘NR==1{print $1}’ file #-F选项直接接冒号
而不能是这样:
② gawk ‘NR==1{FS=”:” ; if(NF>=2) print $1}’ file
第一种,在读取数据流之前就已经对输入字段的分隔符进行了更改,这样读取第一个数据流时,NF是成功按照“:”进行字段分隔计数的。
第二种,在读取数据流之后,匹配之后才做输入字段分隔符的更改。此时,NF已经固定。在读取过程中,NF的数值已经按照默认分隔符进行分隔并计数,所以此时更改对它无效。
如果有多个字段分隔符,就可以用正则表达式了:
gawk -F’[ :\t]’ ‘NR==1{print $1}’ file
-F后面接的是一个单引号括起来的正则表达式“[ ]”符号,中间列举的均为输入字段分隔符:空格、冒号和制表符
在写上面命令时需要注意的是:
⑴ 特殊符号的赋值,两端要打双引号,比如FS=”:”
⑵ 不同action之间要加“;”(分号),比如②中的FS=”:” ; if……
其它的比如:输入记录分隔符、输出记录分隔符、输出字段分隔符等,都可以按照上面这个模式进行更改。
4.2 函数
gawk提供了一些用来操作数字和字符串的函数
表4.2 gawk提供的一些函数 | |
函数 | 含义 |
length(str) | 返回str中的字符个数;如果没有带str参数,则返回当前记录中的字符个数 |
int(num) | 返回num的整数部分 |
index(str1,str2) | 返回str2在str1中的索引,如果str2不存在就返回0 |
split(str,arr,del) | 用del作为分隔符,将str的元素放在数组arr[1]…arr[n]中,返回数组中的元素个数 |
sprint(fmt,args) | 根据fmt格式化args并返回格式化后的字符串 |
substr(str,pos.len) | 返回str中从pos开始,长度为len个字符的子字符串 |
tolower(str) | 返回str的副本,但是其中所有的大写字母被替换成相应的小写字母 |
toupper(str) | 返回str的副本,但是其中所有的小写字母被替换成相应得大写字母 |
比如:gawk ‘NR==1{print length}’ file
如果是默认记录分隔符,则返回第一行的字符个数。包括空格等符号。
4.3 算术运算符
awk中很多都是借鉴C语言,算术运算符也都是来自C
表4.3 算术运算符 | |
运算符 | 含义 |
** | 幂 |
* | 乘积 |
/ | 除 |
% | 取余 |
+ | 加 |
- | 减 |
= | 赋值 |
++ | 运算符前面的变量自增 |
-- | 运算符前面的变量递减 |
+=、-=、*=、/=、%= | 前面表达式与后面表达式相作用,结果赋予前面变量 |
4.4控制语句
gawk支持的控制结构:if…else、while、for
两个辅助:break、continue(用法和C语言完全一样)
三种控制结构和C语言中的用法也是大同小异。
① if…else
if(condition){commands} else{commands}
例如:
gawk ‘if($2>5){print $0} else{print “hello”}’ file
② while
while(condition){commands}
③ for
for有两种结构:
一种是普通的:for(init; condition; increment) {commands}
另一种是处理关联数组时:for(var in array){commands}
在处理关联数组时,和shell的for控制结构处理方式很像。
处理方式是:遍历关联数组array,每循环一次,都将array相应元素赋给var变量。
表4.4 控制语句常用的关系运算符 | |
运算符 | 含义 |
< | 小于 |
<= | 小于等于 |
== | 等于 |
!= | 不等于 |
>= | 大于等于 |
> | 大于 |
控制语句支持:变量、布尔运算符,和关系运算符等等(和C语言中的控制语句支持的操作是一样的)
其中布尔运算符在pattern中也是被支持的。
参考:
《Linux命令、编辑器与shell编程》Mark G.Sobell著 包战,孙向华,胡艮胜 译
《UNIX shell范例精解》(第4版).Ellie Quigley著 李化,张国强 译 (这本书对awk、sed介绍的很全)