gawk编辑器详解
文章目录
介绍
虽然sed编辑器是非常方便自动修改文本文件的工具,但其也有自身的限制。通常你需要一 个用来处理文件中的数据的更高级工具,它能提供一个类编程环境来修改和重新组织文件中的数 据。这正是gawk能够做到的。
gawk程序是Unix中的原始awk程序的GNU版本。gawk程序让流编辑迈上了一个新的台阶,它 提供了一种编程语言而不只是编辑器命令。在gawk编程语言中,你可以做下面的事情:
- 定义变量来保存数据;
- 使用算术和字符串操作符来处理数据;
- 使用结构化编程概念(比如if-then语句和循环)来为数据处理增加处理逻辑;
- 通过提取数据文件中的数据元素,将其重新排列或格式化,生成格式化报告。
gawk程序的报告生成能力通常用来从大文本文件中提取数据元素,并将它们格式化成可读的 报告。其中最完美的例子是格式化日志文件。在日志文件中找出错误行会很难,gawk程序可以让 你从日志文件中过滤出需要的数据元素,然后你可以将其格式化,使得重要的数据更易于阅读。
gawk程序的基本格式如下:
gawk options program file
gawk的强大之处在于程序脚本。可以写脚本来读取文本行的数据,然后处理并显示数据,创 建任何类型的输出报告。
使用方式
从命令行读取程序脚本
gawk程序脚本用一对花括号来定义。你必须将脚本命令放到两个花括号({``}
)中。
$ gawk '(print "Hello World!"}'
这个程序脚本定义了一个命令:print命令。这个命令名副其实:它会将文本打印到STDOUT。
如果尝试运行这个命令,你可能会有些失望,因为什么都不会发生。原因在于没有在命令行上指 定文件名,所以gawk程序会从STDIN接收数据。在运行这个程序时,它会一直等待从STDIN输入 的文本。如果你输入一行文本并按下回车键,gawk会对这行文本运行一遍程序脚本。
要终止这个gawk程序,你必须表明数据流已经结束了。bash shell提供了一个组合键来生成 EOF(End-of-File)字符。Ctrl+D组合键会在bash中产生一个EOF字符。这个组合键能够终止该gawk 程序并返回到命令行界面提示符下。
使用数据字段变量
gawk的主要特性之一是其处理文本文件中数据的能力。它会自动给一行中的每个数据元素分 配一个变量。默认情况下,gawk会将如下变量分配给它在文本行中发现的数据字段:
- $0代表整个文本行;
- $1代表文本行中的第1个数据字段;
- $2代表文本行中的第2个数据字段;
- $n代表文本行中的第n个数据字段。
在文本行中,每个数据字段都是通过字段分隔符划分的。gawk在读取一行文本时,会用预定 义的字段分隔符划分每个数据字段。gawk中默认的字段分隔符是任意的空白字符(例如空格或制 表符)。
如果你要读取采用了其他字段分隔符的文件,可以用-F选项指定。
gawk -F: '{print $1}' /etc/passwd
这个简短的程序显示了系统中密码文件的第1个数据字段。由于/etc/passwd文件用冒号来分隔 数字字段,因而如果要划分开每个数据元素,则必须在gawk选项中将冒号指定为字段分隔符。
在程序脚本中使用多个命令
gawk编程语言允许你将多条 命令组合成一个正常的程序。要在命令行上的程序脚本中使用多条命令,只要在命令之间放个分号即可。
|
|
第一条命令会给字段变量$4赋值。第二条命令会打印整个数据字段。注意, gawk程序在输 出中已经将原文本中的第四个数据字段替换成了新值。
也可以用次提示符一次一行地输入程序脚本命令。
$ gawk '{
> $4="Christine"
> print $0}'
My name is Rich
My name is Christine $
在你用了表示起始的单引号后,bash shell会使用次提示符来提示你输入更多数据。你可以每次在每行加一条命令,直到输入了结尾的单引号。因为没有在命令行中指定文件名,gawk程序会 从STDIN中获得数据。当运行这个程序的时候,它会等着读取来自STDIN的文本。要退出程序,只需按下Ctrl+D组合键来表明数据结束。
从文件中读取程序
跟sed编辑器一样,gawk编辑器允许将程序存储到文件中,然后再在命令行中引用。
|
|
可以在程序文件中指定多条命令。要这么做的话,只要一条命令放一行即可,不需要用分号。
|
|
注意,gawk 程序在引用变量值时并未像shell脚本一样使用美元符。
在处理数据前运行脚本
gawk还允许指定程序脚本何时运行。默认情况下,gawk会从输入中读取一行文本,然后针 对该行的数据执行程序脚本。有时可能需要在处理数据前运行脚本,比如为报告创建标题。BEGIN 关键字就是用来做这个的。它会强制gawk在读取数据前执行BEGIN关键字后指定的程序脚本。
$ gawk 'BEGIN {print "The data3 File Contents:"}
> {print $0}' data3.txt
这次print命令会在读取数据前显示文本。在gawk执行了BEGIN脚本后,它会用第二段脚本来处理文件数据。这么做时要小心,两段 脚本仍然被认为是gawk命令行中的一个文本字符串。你需要相应地加上单引号。
在处理数据后运行脚本
与BEGIN关键字类似,END关键字允许你指定一个程序脚本,gawk会在读完数据后执行它。
$ gawk 'BEGIN {print "The data3 File Contents:"}
> {print $0}
> END {print "End of File"}' data3.txt
当gawk程序打印完文件内容后,它会执行END脚本中的命令。这是在处理完所有正常数据后给报告添加页脚的最佳方法。
使用变量
内建变量
1. 字段和记录分隔符变量
数据字段变量允许你使用美元符号($)和字段在该记录中的位置值来引用记录对应的字段。因此,要引用记录中的第一个数 据字段,就用变量$1;要引用第二个字段,就用$2,依次类推。
数据字段是由字段分隔符来划定的。默认情况下,字段分隔符是一个空白字符,也就是空格 符或者制表符。
内建变量FS是一组内建变量中的一个,这组变量用于控制gawk如何处理输入输出数据中的 字段和记录。
变量FS和OFS定义了gawk如何处理数据流中的数据字段。你已经知道了如何使用变量FS来定 义记录中的字段分隔符。变量OFS具备相同的功能,只不过是用在print命令的输出上。
默认情况下,gawk将OFS设成一个空格,所以如果你用命令:
print $1,$2,$3
会看到如下输出:
field1 field2 field3
在下面的例子里,你能看到这点。
$ cat data1
data11,data12,data13,data14,data15
data21,data22,data23,data24,data25
data31,data32,data33,data34,data35
$ gawk 'BEGIN{FS=","} {print $1,$2,$3}' data1
data11 data12 data13
data21 data22 data23
data31 data32 data33
$
print命令会自动将OFS变量的值放置在输出中的每个字段间。通过设置OFS变量,可以在输 出中使用任意字符串来分隔字段。
gawk 'BEGIN{FS=","; OFS="-"} {print $1,$2,$3}' data1
FIELDWIDTHS变量允许你不依靠字段分隔符来读取记录。在一些应用程序中,数据并没有使 用字段分隔符,而是被放置在了记录中的特定列。这种情况下,必须设定FIELDWIDTHS变量来 匹配数据在记录中的位置。
一旦设置了FIELDWIDTH变量,gawk就会忽略FS变量,并根据提供的字段宽度来计算字段。 下面是个采用字段宽度而非字段分隔符的例子。
$ gawk 'BEGIN{FIELDWIDTHS="3 5 2 5"}{print $1,$2,$3,$4}' data1b
一定要记住,一旦设定了FIELDWIDTHS变量的值,就不能再改变了。这种方法并不适用 于变长的字段。
变量RS和ORS定义了gawk程序如何处理数据流中的字段。默认情况下,gawk将RS和ORS设为 换行符。默认的RS值表明,输入数据流中的每行新文本就是一条新纪录。
有时,你会在数据流中碰到占据多行的字段。典型的例子是包含地址和电话号码的数据,其 中地址和电话号码各占一行。
Riley Mullen
123 Main Street
Chicago, IL 60601
(312)555-1234
如果你用默认的FS和RS变量值来读取这组数据,gawk就会把每行作为一条单独的记录来读 取,并将记录中的空格当作字段分隔符。这可不是你希望看到的。
要解决这个问题,只需把FS变量设置成换行符。这就表明数据流中的每行都是一个单独的字 段,每行上的所有数据都属于同一个字段。但现在令你头疼的是无从判断一个新的数据行从何开始。
对于这一问题,可以把RS变量设置成空字符串,然后在数据记录间留一个空白行。gawk会 把每个空白行当作一个记录分隔符。
2. 数据变量
除了字段和记录分隔符变量外,gawk还提供了其他一些内建变量来帮助你了解数据发生了什 么变化,并提取shell环境的信息。
ARGC和ARGV变量允许从shell中 获得命令行参数的总数以及它们的值。但这可能有点麻烦,因为gawk并不会将程序脚本当成命令 行参数的一部分。
$ gawk 'BEGIN{print ARGC,ARGV[1]}' data1
ARGC变量表明命令行上有两个参数。这包括gawk命令和data1参数(记住,程序脚本并不算参数)。ARGV数组从索引0开始,代表的是命令。第一个数组值是gawk命令后的第一个命令行参数。
跟shell变量不同,在脚本中引用gawk变量时,变量名前不加美元符.
ENVIRON变量看起来可能有点陌生。它使用关联数组来提取shell环境变量。关联数组用文本作为数组的索引值,而不是数值。
数组索引中的文本是shell环境变量名,而数组的值则是shell环境变量的值。
print ENVIRON["HOME"]
ENVIRON[“HOME”]变量从shell中提取了HOME环境变量的值。类似地ENVIRON[“PATH”]提 取了PATH环境变量的值。可以用这种方法来从shell中提取任何环境变量的值,以供gawk程序使用。
NF变量含有数据文件中最后一个数据字段的数字值。可以在它前面加个美元符将其用作字段 变量。
FNR变量含有当前数据文件中已处理过的记录数, NR变量则含有已处理过的记录总数。
如果只使用一个数据文件作为输入, FNR和NR的值是相同的;如果使用多 个数据文件作为输入,FNR的值会在处理每个数据文件时被重置,而NR的值则会继续计数直到处 理完所有的数据文件。
自定义变量
gawk自定义 变量名可以是任意数目的字母、数字和下划线,但不能以数字开头。重要的是,要记住gawk变 量名区分大小写。
1. 在脚本中给变量赋值
在gawk程序中给变量赋值跟在shell脚本中赋值类似,都用赋值语句。
testing="This is a test"
赋值语句还可以包含数学算式来处理数字值。
$ gawk 'BEGIN{x=4; x= x * 2 + 3; print x}'
gawk编程语言包含了用来处理数字值的标准算数操作符。其中包 括求余符号(%)和幂运算符号(^或**)。
2. 在命令行上给变量赋值
也可以用gawk命令行来给程序中的变量赋值。这允许你在正常的代码之外赋值,即时改变变量的值。
|
|
这个特性可以让你在不改变脚本代码的情况下就能够改变脚本的行为。
使用命令行参数来定义变量值会有一个问题。在你设置了变量后,这个值在代码的BEGIN部 分不可用。
可以用-v命令行参数来解决这个问题。它允许你在BEGIN代码之前设定变量。在命令行上, -v命令行参数必须放在脚本代码之前。
使用模式
正则表达式
在使用正则表达式时,正则表达式必须出现在它要控制的程序脚本的左花括号前。
$ gawk 'BEGIN{FS=","} /11/{print $1}' data1
data11
$
正则表达式/11/匹配了数据字段中含有字符串11的记录。gawk程序会用正则表达式对记录中所有的数据字段进行匹配,包括字段分隔符。
$ gawk 'BEGIN{FS=","} /,d/{print $1}' data1
data11
data21
data31
$
这个例子使用正则表达式匹配了用作字段分隔符的逗号。这也并不总是件好事。它可能会造 成如下问题:当试图匹配某个数据字段中的特定数据时,这些数据又出现在其他数据字段中。如 果需要用正则表达式匹配某个特定的数据实例,应该使用匹配操作符。
匹配操作符
匹配操作符(matching operator)允许将正则表达式限定在记录中的特定数据字段。匹配操作符是波浪线(~)。可以指定匹配操作符、数据字段变量以及要匹配的正则表达式。
$1 ~ /^data/
$1变量代表记录中的第一个数据字段。这个表达式会过滤出第一个字段以文本data开头的 所有记录.
你也可以用!符号来排除正则表达式的匹配。如果记录中没有找到匹配正则表达式的文本,程序脚本就会作用到记录数据。
$ gawk –F: '$1 !~ /rich/{print $1,$NF}' /etc/passwd
在这个例子中,gawk程序脚本会打印/etc/passwd文件中与用户ID rich不匹配的用户ID和登 录shell。
数学表达式
除了正则表达式,你也可以在匹配模式中用数学表达式。这个功能在匹配数据字段中的数字 值时非常方便。
$ gawk -F: '$4 == 0{print $1}' /etc/passwd
这段脚本会查看第四个数据字段含有值0的记录。
可以使用任何常见的数学比较表达式。
- x == y:值x等于y。
- x <= y:值x小于等于y。
- x < y:值x小于y。
- x >= y:值x大于等于y。
- x > y:值x大于y。
也可以对文本数据使用表达式,但必须小心。跟正则表达式不同,表达式必须完全匹配。数 据必须跟模式严格匹配。
结构化命令
if语句
gawk编程语言支持标准的if-then-else格式的if语句。你必须为if语句定义一个求值的 条件,并将其用圆括号括起来。如果条件求值为TRUE,紧跟在if语句后的语句会执行。如果条 件求值为FALSE,这条语句就会被跳过。可以用这种格式:
if (condition)
statement1
如果需要在if语句中执行多条语句,就必须用花括号将它们括起来。
|
|
gawk的if语句也支持else子句,允许在if语句条件不成立的情况下执行一条或多条语句。
可以在单行上使用else子句,但必须在if语句部分之后使用分号。
if (condition) statement1; else statement2
while语句
while语句为gawk程序提供了一个基本的循环功能。下面是while语句的格式。
while (condition)
{
statements
}
while循环允许遍历一组数据,并检查迭代的结束条件。
gawk编程语言支持在while循环中使用break语句和continue语句,允许你从循环中跳出。
|
|
do-while语句
do-while语句类似于while语句,但会在检查条件语句之前执行命令。下面是do-while语 句的格式。
do {
statements
} while (condition)
这种格式保证了语句会在条件被求值之前至少执行一次。当需要在求值条件前执行语句时, 这个特性非常方便。
for语句
for语句是许多编程语言执行循环的常见方法。gawk编程语言支持C风格的for循环。
for( variable assignment; condition; iteration process)
内建函数
gawk编程语言提供了不少内置函数,可进行一些常见的数学、字符串以及时间函数运算。你 可以在gawk程序中利用这些函数来减少脚本中的编码工作。
数学函数
虽然数学函数的数量并不多,但gawk提供了标准数学运算中要用到的一些基本元素。
int() 函数会生成一个值的整数部分,但它并不会四舍五入取近似值。它的做法更像其他编程语言中的 floor函数。它会生成该值和0之间最接近该值的整数。这意味着int()函数在值为5.6时返回5,在值为-5.6时则返回-5。
rand()函数非常适合创建随机数,但你需要用点技巧才能得到有意义的值。rand()函数会返回一个随机数,但这个随机数只在0和1之间(不包括0或1)。要得到更大的数,就需要放大返回值。
产生较大整数随机数的常见方法是用rand()函数和int()函数创建一个算法。
x = int(10 * rand())
这会返回一个0~9(包括0和9)的随机整数值。只要为你的程序用上限值替换掉等式中的10 就可以了。
在使用一些数学函数时要小心,因为gawk语言对于它能够处理的数值有一个限定区间。如果 超出了这个区间,就会得到一条错误消息。
字符串函数
asort和asorti函数是新加入的gawk函数,允许你基于数据元素值(asort)或索引值(asorti)对数组变量进行排序。这里有个使用asort的例子。
|
|
新数组test含有排序后的原数组的数据元素,但索引值现在变为表明正确顺序的数字值了。
split函数是将数据字段放到数组中以供进一步处理的好办法。
|
|
新数组使用连续数字作为数组索引,从含有第一个数据字段的索引值1开始。
时间函数
gawk编程语言包含一些函数来帮助处理时间值
时间函数常用来处理日志文件,而日志文件则常含有需要进行比较的日期。通过将日期的文 本表示形式转换成epoch时间(自1970-01-01 00:00:00 UTC到现在的秒数),可以轻松地比较日期。 下面是在gawk程序中使用时间函数的例子。
$ gawk 'BEGIN{
> date = systime()
> day = strftime("%A, %B %d, %Y", date)
> print day
> }'
Friday, December 26, 2014
$
该例用systime函数从系统获取当前的epoch时间戳,然后用strftime函数将它转换成用户 可读的格式,转换过程中使用了shell命令date的日期格式化字符。
自定义函数
定义函数
要定义自己的函数,必须用function关键字。
function name([variables])
{
statements
}
函数名必须能够唯一标识函数。可以在调用的gawk程序中传给这个函数一个或多个变量。
function printthird()
{
print $3
}
这个函数会打印记录中的第三个数据字段。
函数还能用return语句返回值:
return value
值可以是变量,或者是最终能计算出值的算式:
function myrand(limit)
{
return int(limit * rand())
}
你可以将函数的返回值赋给gawk程序中的一个变量:
x = myrand(100)
这个变量包含函数的返回值。
使用自定义函数
在定义函数时,它必须出现在所有代码块之前(包括BEGIN代码块)。乍一看可能有点怪异, 但它有助于将函数代码与gawk程序的其他部分分开。
创建函数库
gawk提供了一种途径来将多个函数 放到一个库文件中.
首先,你需要创建一个存储所有gawk函数的文件。
$ cat funclib
function myprint()
{
printf "%-16s - %s\n", $1, $4
}
function myrand(limit)
{
return int(limit * rand())
}
function printthird()
{
print $3
}
$
funclib文件含有三个函数定义。需要使用-f命令行参数来使用它们。很遗憾,不能将-f命令行参数和内联gawk脚本放到一起使用,不过可以在同一个命令行中使用多个-f参数。
因此,要使用库,只要创建一个含有你的gawk程序的文件,然后在命令行上同时指定库文件 和程序文件就行了。
$ gawk -f funclib -f script4 data2
你要做的是当需要使用库中定义的函数时,将funclib文件加到你的gawk命令行上就可以了。
文章作者 Forz
上次更新 2017-08-11