Shell 使用awk进行高级文本处理,awk
命令可以处理数据流。它支持关联数组、递归函数、条件语句等功能。
预备知识
awk
脚本的结构如下:
awk
命令也可以从stdin
中读取输入。
awk
脚本通常由3部分组成:BEGIN
、END
和带模式匹配选项的公共语句块(common statement block)。这3个部分都是可选的,可以不用出现在脚本中。
awk
以逐行的形式处理文件。BEGIN
之后的命令会先于公共语句块执行。对于匹配PATTERN
的行,awk
会对其执行PATTERN
之后的命令。最后,在处理完整个文件之后,awk
会执行END
之后的命令。
实战演练
简单的awk
脚本可以放在单引号或双引号中:
或者
下面的命令会输出文件行数:
或者
工作原理
awk
命令的工作方式如下。
(1) 首先执行BEGIN { commands }
语句块中的语句。
(2) 接着从文件或stdin
中读取一行,如果能够匹配pattern
,则执行随后的commands
语句块。重复这个过程,直到文件全部被读取完毕。
(3) 当读至输入流末尾时,执行END { commands }
语句块。
BEGIN
语句块在awk
开始从输入流中读取行之前被执行。这是一个可选的语句块,诸如变量初始化、打印输出表格的表头等语句通常都可以放在BEGIN
语句块中。
END
语句块和BEGIN
语句块类似。它在awk
读取完输入流中所有的行之后被执行。像打印所有行的分析结果这种常见任务都是在END
语句块中实现的。
最重要的部分就是和pattern
关联的语句块。这个语句块同样是可选的。如果不提供,则默认执行{ print }
,即打印所读取到的每一行。awk
对于读取到的每一行都会执行该语句块。这就像一个用来读取行的while
循环,在循环体中提供了相应的语句。
每读取一行,awk
就会检查该行是否匹配指定的模式。模式本身可以是正则表达式、条件语句以及行范围等。如果当前行匹配该模式,则执行{ }
中的语句。模式是可选的。如果没有提供模式,那么awk
就认为所有的行都是匹配的:
当使用不带参数的print
时,它会打印出当前行。print
能够接受参数。这些参数以逗号分隔,在打印参数时则以空格作为参数之间的分隔符。在awk
的print
语句中,双引号被当作拼接操作符(concatenation operator)使用。例如:
该命令输出如下:
echo
命令向标准输出写入一行,因此awk
的 { }
语句块中的语句只被执行一次。如果awk
的输入中包含多行,那么 { }
语句块中的语句也就会被执行相应的次数。拼接的使用方法如下:
该命令输出如下:
{ }
就像一个循环体,对文件中的每一行进行迭代。
我们通常将变量初始化语句(如
var=0;
)放入BEGIN
语句块中。在END{}
语句块中,往往会放入用于打印结果的语句。
补充内容
awk
命令与诸如grep
、find
和tr
这类命令不同,它功能众多,而且拥有很多能够更改命令行为的选项。awk
命令是一个解释器,它能够解释并执行程序,和shell一样,它也包括了一些特殊变量。
- 特殊变量
以下是awk
可以使用的一些特殊变量。
NR
:表示记录编号,当awk
将行作为记录时,该变量相当于当前行号。NF
:表示字段数量,在处理当前记录时,相当于字段数量。默认的字段分隔符是空格。$0
:该变量包含当前记录的文本内容。$1
:该变量包含第一个字段的文本内容。$2
:该变量包含第二个字段的文本内容。
例如:
我们可以用print $NF
打印一行中最后一个字段,用 $(NF-1)
打印倒数第二个字段,其他字段以此类推。awk
也支持printf()
函数,其语法和C语言中的同名函数一样。下面的命令会打印出每一行的第二和第三个字段:
我们可以使用NR统计文件的行数:
这里只用到了END
语句块。每读入一行,awk
都会更新NR
。当到达文件末尾时,NR
中的值就是最后一行的行号。你可以将每一行中第一个字段的值按照下面的方法累加:
- 将外部变量值传递给
awk
借助选项-v
,我们可以将外部值(并非来自stdin
)传递给awk
:
还有另一种灵活的方法可以将多个外部变量传递给awk
。例如:
当输入来自于文件而非标准输入时,使用下列命令:
在上面的方法中,变量以键-值对的形式给出,使用空格分隔(v1=var1 v2=var2
),作为awk
的命令行参数紧随在BEGIN
、{}
和END
语句块之后。
- 用
getline
读取行
awk
默认读取文件中的所有行。如果只想读取某一行,可以使用getline
函数。它可以用于在BEGIN
语句块中读取文件的头部信息,然后在主语句块中处理余下的实际数据。该函数的语法为:getline var
。变量var
中包含了特定行。如果调用时不带参数,我们可以用 $0
、$1
和$2
访问文本行的内容。例如:
- 使用过滤模式对
awk
处理的行进行过滤
我们可以为需要处理的行指定一些条件:
- 设置字段分隔符
默认的字段分隔符是空格。我们也可以用选项-F
指定不同的分隔符:
或者
在BEGIN
语句块中可以用OFS="delimiter"
设置输出字段分隔符。
- 从
awk
中读取命令输出
awk
可以调用命令并读取输出。把命令放入引号中,然后利用管道将命令输出传入getline
:
下面的代码从/etc/passwd文件中读入一行,然后显示出用户登录名及其主目录。在BEGIN
语句块中将字段分隔符设置为:
,在主语句块中调用了grep
。
awk
的关联数组
除了数字和字符串类型的变量,awk
还支持关联数组。关联数组是一种使用字符串作为索引的数组。你可以通过中括号中索引的形式来分辨出关联数组:
就像用户定义的简单变量一样,你也可以使用等号为数组元素赋值:
- 在
awk
中使用循环
在awk
中可以使用for
循环,其格式与C语言中的差不多:
另外awk
还支持列表形式的for
循环,也可以显示出数组的内容:
下面的例子展示了如何将收集到的数据存入数组并显示出来。这个脚本从/etc/password中读取文本行,以:
作为分隔符将行分割成字段,然后创建一个关联数组,数组的索引是登录ID,对应的值是用户名:
awk
内建的字符串处理函数
awk
有很多内建的字符串处理函数。
length(string)
:返回字符串string
的长度。index(string, search_string)
:返回search_string
在字符串string
中出现的位置。split(string, array, delimiter)
:以delimiter
作为分隔符,分割字符串string
,将生成的字符串存入数组array
。substr(string, start-position, end-position)
:返回字符串string
中以start-position
和end-position
作为起止位置的子串。sub(regex, replacement_str, string)
:将正则表达式regex
匹配到的第一处内容替换成replacment_str
。gsub(regex, replacement_str, string)
:和sub()
类似。不过该函数会替换正则表达式regex
匹配到的所有内容。match(regex, string)
:检查正则表达式regex
是否能够在字符串string
中找到匹配。如果能够找到,返回非0值;否则,返回0。match()
有两个相关的特殊变量,分别是RSTART
和RLENGTH
。变量RSTART
包含了匹配内容的起始位置,而变量RLENGTH
包含了匹配内容的长度。