shell脚本编程笔记(十二)—— gawk程序

一、 简介

虽然sed是非常方便自动修改文本文件的工具,但其也有自身的限制。通常你需要一个用来处理文件中的数据的更高级工具,它能提供一个类编程环境来修改和重新组织文件中的数据,这正是gawk能够做到的。

gawk程序是Unix中的原始awk程序的GNU版本,它让流编辑迈上了一个新的台阶,提供了一种编程语言而不只是编辑器命令。

gawk中,你可以做下面的事情:

  • 定义变量来保存数据;
  • 使用算术和字符串操作符来处理数据;
  • 使用结构化编程概念(比如if-then语句和循环)来为数据处理增加处理逻辑;
  • 通过提取数据文件中的数据元素,将其重新排列或格式化,生成格式化报告。

gawk程序的报告生成能力通常用来从大文本文件中提取数据元素,并将它们格式化成可读的报告。其中最完美的例子是格式化日志文件。在日志文件中找出错误行会很难, gawk程序可以让你从日志文件中过滤出需要的数据元素,然后你可以将其格式化,使得重要的数据更易于阅读。

 

二、 gawk入门

gawk程序的基本格式如下:gawk options program file

命令行选项提供了一个简单的途径来定制gawk程序中的功能。gawk的强大之处在于程序脚本。可以写脚本来读取文本行的数据,然后处理并显示数据,创
建任何类型的输出报告。

 

1. 从命令行读取程序脚本

gawk程序脚本用一对花括号来定义。你必须将脚本命令放到{}中,由于gawk命令行假定脚本是单个文本字符串,你还必须将脚本放到单引号中。

gawk '{print "Hello World!"}'

如果尝试运行这个命令,你可能会有些失望,因为什么都不会发生。原因在于没有在命令行上指定文件名,所以gawk程序会从STDIN接收数据。在运行这个程序时,它会一直等待从STDIN输入的文本。

如果你输入一行文本并按下回车键, gawk会对这行文本运行一遍程序脚本。跟sed编辑器一样, gawk程序会针对数据流中的每行文本执行程序脚本。由于程序脚本被设为显示一行固定的文本字符串,因此不管你在数据流中输入什么文本,都会得到同样的文本输出。

 

2. 使用数据字段变量

gawk的主要特性之一是其处理文本文件中数据的能力。它会自动给一行中的每个数据元素分配一个变量。默认情况下, gawk会将如下变量分配给它在文本行中发现的数据字段:

  • $0代表整个文本行;
  • $1代表文本行中的第1个数据字段;
  • $2代表文本行中的第2个数据字段;
  • $n代表文本行中的第n个数据字段。

在文本行中,每个数据字段都是通过字段分隔符划分的。 gawk在读取一行文本时,会用预定义的字段分隔符划分每个数据字段。 gawk中默认的字段分隔符是任意的空白字符(例如空格或制表符)。

在下面的例子中, gawk程序读取文本文件,只显示第1个数据字段的值。

cat data2.txt
One line of test text.
Two lines of test text.
Three lines of test text.

gawk '{print $1}' data2.txt
One
Two
Three

如果你要读取采用了其他字段分隔符的文件,可以用-F选项指定。

gawk -F: '{print $1}' /etc/passwd

 

3. 在程序脚本中使用多个命令

gawk允许你将多条命令组合成一个正常的程序。要在命令行上的程序脚本中使用多条命令,只要在命令之间放个分号即可。

echo "My name is Rich" | gawk '{$4="Christine"; print $0}'

第一条命令会给字段变量$4赋值。第二条命令会打印整个数据字段。

也可以用次提示符一次一行地输入程序脚本命令。

$ gawk '{
> $4="Christine"
> print $0}'
My name is Rich
My name is Christine

在你用了表示起始的单引号后, bash shell会使用次提示符来提示你输入更多数据。你可以每次在每行加一条命令,直到输入了结尾的单引号。因为没有在命令行中指定文件名,gawkSTDIN中获得数据。当运行这个程序的时候,它会等着读取来自STDIN的文本。

 

4. 从文件中读取程序

sed一样, gawk允许将程序存储到文件中,然后再在命令行中引用。

cat script2.gawk
{print $1 "'s home directory is " $6}

gawk -F: -f script2.gawk /etc/passwd
root's home directory is /root
bin's home directory is /bin
daemon's home directory is /sbin
adm's home directory is /var/adm
lp's home directory is /var/spool/lpd
[...]

可以在程序文件中指定多条命令,只要一条命令放一行即可,不需要用分号。

cat script3.gawk
{
text = "'s home directory is "
print $1 text $6
}

gawk -F: -f script3.gawk /etc/passwd
root's home directory is /root
bin's home directory is /bin
daemon's home directory is /sbin
adm's home directory is /var/adm
lp's home directory is /var/spool/lpd
[...]

 

5. 在处理数据前运行脚本

gawk还允许指定程序脚本何时运行。默认情况下,gawk会从输入中读取一行文本,然后针对该行的数据执行程序脚本。有时可能需要在处理数据前运行脚本,比如为报告创建标题。BEGIN关键字就是用来做这个的。它会强制gawk在读取数据前执行BEGIN关键字后指定的程序脚本。

cat data3.txt
Line 1
Line 2
Line 3

gawk 'BEGIN {print "The File Contents:"}
> {print $0}' data3.txt
The data3 File Contents:
Line 1
Line 2
Line 3

或者

gawk 'BEGIN {print "The File Contents:"};{print $0}' /etc/passwd

 

6. 在处理数据后运行脚本

BEGIN类似, END关键字允许你指定一个程序脚本, gawk会在读完数据后执行它。

gawk 'BEGIN {print "The data3 File Contents:"}
> {print $0}
> END {print "End of File"}' data3.txt
The data3 File Contents:
Line 1

# 或者
gawk 'BEGIN {print "The File Contents:"};{print $0};END {print "End of File"}' /etc/passwd

gawk程序打印完文件内容后,它会执行END脚本中的命令。这是在处理完所有正常数据后给报告添加页脚的最佳方法。

可以将所有这些内容放到一起组成一个漂亮的小程序脚本文件,用它从一个简单的数据文件中创建一份完整的报告。

cat script4.gawk
BEGIN {
print "The latest list of users and shells"
print " UserID \t Shell"
print "-------- \t -------"
FS=":"
}
{
print $1 " \t " $7
}
END {
print "This concludes the listing"
}

gawk -f script4.gawk /etc/passwd

 

 

三、 使用变量

所有编程语言共有的一个特性是使用变量来存取值,gawk支持两种不同类型的变量:

  • 内建变量
  • 自定义变量

1. 内建变量

gawk使用内建变量来引用程序数据里的一些特殊功能,例如处理数据文件中的数据字段和记录的信息。

  • 字段和记录分隔符变量

变量FSOFS定义了gawk如何处理数据流中的数据字段。你已经知道了如何使用变量FS来定义记录中的字段分隔符。变量OFS具备相同的功能,只不过是用在print命令的输出上。默认情况下, gawkOFS设成一个空格

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
data11-data12-data13
data21-data22-data23
data31-data32-data33

gawk 'BEGIN{FS=","; OFS="<-->"} {print $1,$2,$3}' data1
data11<-->data12<-->data13
data21<-->data22<-->data23
data31<-->data32<-->data33
  • FIELDWIDTHS变量

FIELDWIDTHS变量允许你不依靠字段分隔符来读取记录。在一些应用程序中,数据并没有使用字段分隔符,而是被放置在了记录中的特定列。这种情况下,必须设定FIELDWIDTHS变量来匹配数据在记录中的位置。

一旦设置了FIELDWIDTH变量, gawk就会忽略FS变量,并根据提供的字段宽度来计算字段。

cat data1b
1005.3247596.37
115-2.349194.00
05810.1298100.1

gawk 'BEGIN{FIELDWIDTHS="3 5 2 5"}{print $1,$2,$3,$4}' data1b
100 5.324 75 96.37
115 -2.34 91 94.00
058 10.12 98 100.1

FIELDWIDTHS变量定义了四个字段, gawk依此来解析数据记录。每个记录中的数字串会根据已定义好的字段长度来分割。

  • 变量RSORS

变量RSORS定义了gawk程序如何处理数据流中的字段。默认情况下, gawkRSORS设为换行符。默认的RS值表明,输入数据流中的每行新文本就是一条新纪录。
有时,你会在数据流中碰到占据多行的字段。典型的例子是包含地址和电话号码的数据,其中地址和电话号码各占一行。

cat data2
Riley Mullen
123 Main Street
Chicago, IL 60601

(312)555-1234
Frank Williams
456 Oak Street
Indianapolis, IN 46201
(317)555-9876

Haley Snell
4231 Elm Street
Detroit, MI 48201
(313)555-4938

如果你用默认的FSRS变量值来读取这组数据, gawk就会把每行作为一条单独的记录来读取,并将记录中的空格当作字段分隔符。这可不是你希望看到的。

要解决这个问题,只需把FS变量设置成换行符。这就表明数据流中的每行都是一个单独的字段,每行上的所有数据都属于同一个字段。但现在令你头疼的是无从判断一个新的数据行从何开始。

对于这一问题,可以把RS变量设置成空字符串,然后在数据记录间留一个空白行。 gawk把每个空白行当作一个记录分隔符。

gawk 'BEGIN{FS="\n"; RS=""} {print $1,$4}' data2
Riley Mullen (312)555-1234
Frank Williams (317)555-9876
Haley Snell (313)555-4938
  • 数据变量

除了字段和记录分隔符变量外,gawk还提供了其他一些内建变量来帮助你了解数据发生了什么变化,并提取shell环境的信息。

  • ARGCARGV变量

ARGCARGV变量允许从shell获得命令行参数的总数以及它们的值。但这可能有点麻烦,因为gawk并不会将程序脚本当成命令行参数的一部分。

gawk 'BEGIN{print ARGC,ARGV[1]}' data1
2 data1

ARGC变量表明命令行上有两个参数。这包括gawk命令和data1参数(记住,程序脚本并不算参数)。 ARGV数组从索引0开始,代表的是命令。第一个数组值是gawk命令后的第一个命令行参数。

  • ENVIRON变量

ENVIRON变量看起来可能有点陌生。它使用关联数组来提取shell环境变量。关联数组用文本作为数组的索引值,而不是数值。数组索引中的文本是shell环境变量名,而数组的值则是shell环境变量的值。

gawk '
> BEGIN{
> print ENVIRON["HOME"]
> print ENVIRON["PATH"]
> }'
/home/rich
/usr/local/bin:/bin:/usr/bin:/usr/X11R6/bin

可以用这种方法来从shell中提取任何环境变量的值,以供gawk程序使用。

当要在gawk程序中跟踪数据字段和记录时,变量FNR NFNR用起来就非常方便。有时你并不知道记录中到底有多少个数据字段。NF变量可以让你在不知道具体位置的情况下指定记录中的最后一个数据字段。

gawk 'BEGIN{FS=":"; OFS=":"} {print $1,$NF}' /etc/passwd
rich:/bin/bash
testy:/bin/csh
mark:/bin/bash
dan:/bin/bash
mike:/bin/bash
test:/bin/bash
  • FNR变量与NR变量

FNR变量含有当前数据文件中已处理过的记录数NR变量则含有已处理过的记录总数

gawk '
> BEGIN {FS=","}
> {print $1,"FNR="FNR,"NR="NR}
> END{print "There were",NR,"records processed"}' data1 data1
data11 FNR=1 NR=1
data21 FNR=2 NR=2
data31 FNR=3 NR=3
data11 FNR=1 NR=4
data21 FNR=2 NR=5
data31 FNR=3 NR=6
There were 6 records processed

FNR变量的值在gawk处理第二个数据文件时被重置了,而NR变量则在处理第二个数据文件时继续计数。结果就是:如果只使用一个数据文件作为输入, FNRNR的值是相同的;如果使用多个数据文件作为输入, FNR的值会在处理每个数据文件时被重置,而NR的值则会继续计数直到处理完所有的数据文件。

 

2. 自定义变量

gawk自定义变量名可以是任意数目的字母、数字和下划线,但不能以数字开头。重要的是,要记住gawk变量名区分大小写。

gawk程序中给变量赋值跟在shell脚本中赋值类似,都用赋值语句,变量可以保存数值或文本值。

gawk '
> BEGIN{
> testing="This is a test"
> print testing
> testing=45
> print testing
> }'
This is a test
45

赋值语句还可以包含数学算式来处理数字值。

gawk 'BEGIN{x=4; x= x * 2 + 3; print x}'
11

如你在这个例子中看到的, gawk编程语言包含了用来处理数字值的标准算数操作符。其中包括求余符号(%)和幂运算符号(^**)。


3. 在命令行上给变量赋值

也可以用gawk命令行来给程序中的变量赋值。这允许你在正常的代码之外赋值,即时改变变量的值。下面的例子使用命令行变量来显示文件中特定数据字段。

cat script1
BEGIN{FS=","}
{print $n}

gawk -f script1 n=2 data1
data12
data22
data32

gawk -f script1 n=3 data1
data13
data23
data33

这个特性可以让你在不改变脚本代码的情况下就能够改变脚本的行为。第一个例子显示了文件的第二个字段,第二个则显示了第三个字段,只要在命令行上设置n变量的值就行。

使用命令行参数来定义变量值会有一个问题,这个值在代码的BEGIN部分不可用。

cat script2
BEGIN{print "The starting value is",n; FS=","}
{print $n}

gawk -f script2 n=3 data1
The starting value is
data13
data23
data33

可以用-v命令行参数来解决这个问题,它允许你在BEGIN代码之前设定变量,-v命令行参数必须放在脚本代码之前。

gawk -v n=3 -f script2 data1
The starting value is 3
data13
data23
data33

现在在BEGIN代码部分中的变量n的值已经是命令行上设定的那个值了。


4. 处理数组

gawk使用关联数组提供数组功能。关联数组跟数字数组不同之处在于它的索引值可以是任意文本字符串。每个索引字符串都必须能够唯一地标识出赋给它的数据元素。如果你熟悉其他编程语言的话,就知道这跟散列表和字典是同一个概念。
 

1)定义数组变量

可以用标准赋值语句来定义数组变量。数组变量赋值的格式如下:var[index] = element
其中var是变量名, index是关联数组的索引值, element是数据元素值。在引用数组变量时,必须包含索引值来提取相应的数据元素值。

gawk 'BEGIN{
> capital["Illinois"] = "Springfield"
> print capital["Illinois"]
> }'
Springfield

在引用数组变量时,会得到数据元素的值。数据元素值是数字值时也一样。

gawk 'BEGIN{
> var[1] = 34
> var[2] = 3
> total = var[1] + var[2]
> print total
> }'
37


2)遍历数组变量

关联数组变量的问题在于你可能无法知晓索引值是什么,关联数组的索引可以是任何东西。如果要在gawk中遍历一个关联数组,可以用for语句的一种特殊形式。

for (var in array)
{
statements
}

这个for语句会在每次循环时将关联数组array的下一个索引值赋给变量var,然后执行一
statements。重要的是记住这个变量中存储的是索引值而不是数组元素值。可以将这个变量用作数组的索引,轻松地取出数据元素值。

gawk 'BEGIN{
> var["a"] = 1
> var["g"] = 2
> var["m"] = 3
> var["u"] = 4
> for (test in var)
> {
> print "Index:",test," - Value:",var[test]
> }
> }'
Index: u - Value: 4
Index: m - Value: 3
Index: a - Value: 1
Index: g - Value: 2

注意,索引值不会按任何特定顺序返回,但它们都能够指向对应的数据元素值。你不能指望着返回的值都是有固定的顺序,只能保证索引值和数据值是对应的。


3)删除数组变量

从关联数组中删除数组索引要用一个特殊的命令  delete array[index]

删除命令会从数组中删除关联索引值和相关的数据元素值。一旦从关联数组中删除了索引值,你就没法再用它来提取元素值。

gawk 'BEGIN{
> var["a"] = 1
> var["g"] = 2
> for (test in var)
> {
> print "Index:",test," - Value:",var[test]
> }
> delete var["g"]
> print "---"
> for (test in var)
> print "Index:",test," - Value:",var[test]
> }'
Index: a - Value: 1
Index: g - Value: 2
---
Index: a - Value: 1


四、使用模式

gawk程序支持多种类型的匹配模式来过滤数据记录,这一点跟sed编辑器大同小异。

1. 正则表达式

正则表达式必须出现在它要控制的程序脚本的左花括号前。

gawk 'BEGIN{FS=","} /11/{print $1}' data1
data11

正则表达式/11/匹配了数据字段中含有字符串11的记录。 gawk程序会用正则表达式对记录中所有的数据字段进行匹配,包括字段分隔符。

gawk 'BEGIN{FS=","} /,d/{print $1}' data1
data11
data21
data31

这个例子使用正则表达式匹配了用作字段分隔符的逗号。这也并不总是件好事。它可能会造成:当试图匹配某个数据字段中的特定数据时,这些数据又出现在其他数据字段中。如果需要用正则表达式匹配某个特定的数据实例,应该使用匹配操作符。

 

2. 匹配操作符

匹配操作符允许将正则表达式限定在记录中的特定数据字段。匹配操作符是波浪线~。可以指定匹配操作符、数据字段变量以及要匹配的正则表达式。

$1 ~ /^data/

$1变量代表记录中的第一个数据字段。这个表达式会过滤出第一个字段以文本data开头的所有记录。这可是件强大的工具, gawk中经常用它在数据文件中搜索特定的数据元素。

gawk -F: '$1 ~ /rich/{print $1,$NF}' /etc/passwd
rich /bin/bash

你也可以用!符号来排除正则表达式的匹配。

$1 !~ /expression/

如果记录中没有找到匹配正则表达式的文本,程序脚本就会作用到记录数据。

gawk –F: '$1 !~ /rich/{print $1,$NF}' /etc/passwd
root /bin/bash
daemon /bin/sh
bin /bin/sh
sys /bin/sh
--- output truncated ---


3. 数学表达式

除了正则表达式,你也可以在匹配模式中用数学表达式。这个功能在匹配数据字段中的数字值时非常方便。举个例子,如果你想显示所有属于root用户组(组ID0)的系统用户,可以用这个脚本。

gawk -F: '$4 == 0{print $1}' /etc/passwd
root
sync
shutdown
halt
operator

可以使用任何常见的数学比较表达式:== <= < >=>

也可以对文本数据使用表达式,但必须小心。跟正则表达式不同,表达式必须完全匹配。数据必须跟模式严格匹配。

$ gawk -F, '$1 == "data"{print $1}' data1
$
$ gawk -F, '$1 == "data11"{print $1}' data1
data11

第一个测试没有匹配任何记录,因为第一个数据字段的值不在任何记录中。第二个测试用值data11匹配了一条记录。

 

五、 结构化命令

1. if 语句

if (condition)
statement1
# 也可以将它放在一行上,像这样:
if (condition) statement1

下面这个简单的例子演示了这种格式的。

$ cat data4
10
5
13
50
34
$ gawk '{if ($1 > 20) print $1}' data4
50
34

如果需要在if语句中执行多条语句,就必须用花括号将它们括起来。gawkif语句也支持else子句,允许在if语句条件不成立的情况下执行一条或多条语句。

gawk '{
> if ($1 > 20)
> {
> x = $1 * 2
> print x
> } else
> {
> x = $1 / 2
> print x
> }}' data4
5
2.5
6.5
100
68

可以在单行上使用else子句,但必须在if语句部分之后使用分号。

if (condition) statement1; else statement2

以下是上一个例子的单行格式版本,这个格式更紧凑,但也更难理解

gawk '{if ($1 > 20) print $1 * 2; else print $1 / 2}' data4
5
2.5
6.5
100
68

 

2. while 语句

while语句为gawk程序提供了一个基本的循环功能,下面是while语句的格式。

while (condition)
{
statements
}

while循环遍历一组数据,并检查迭代的结束条件。如果在计算中必须使用每条记录中
的多个数据值,这个功能能帮得上忙。

$ cat data5
130 120 135
160 113 140
145 170 215
$ gawk '{
> total = 0
> i = 1
> while (i < 4)
> {
> total += $i
> i++
> }
> avg = total / 3
> print "Average:",avg
> }' data5
Average: 128.333
Average: 137.667
Average: 176.667

while语句会遍历记录中的数据字段,将每个值都加到total变量上,并将计数器变量i增值。当计数器值等于4时, while的条件变成了FALSE,循环结束,然后执行脚本中的下一条语句。这条语句会计算并打印出平均值。这个过程会在数据文件中的每条记录上不断重复。

gawk支持在while循环中使用break语句和continue语句,允许你从循环中跳出。

gawk '{
> total = 0
> i = 1
> while (i < 4)
> {
> total += $i
> if (i == 2)
> break
> i++
> }
> avg = total / 2
> print "The average of the first two data elements is:",avg
> }' data5
The average of the first two data elements is: 125
The average of the first two data elements is: 136.5
The average of the first two data elements is: 157.5

break语句用来在i变量的值为2时从while循环中跳出。
 

3. do-while 语句

do-while语句类似于while语句,但会在检查条件语句之前执行命令。

do
{
statements
} while (condition)

这种格式保证了语句会在条件被求值之前至少执行一次。当需要在求值条件前执行语句时,这个特性非常方便。

gawk '{
> total = 0
> i = 1
> do
> {
> total += $i
> i++
> } while (total < 150)
> print total }' data5
250
160
315


4. for 语句

for语句是许多编程语言执行循环的常见方法。 gawk编程语言支持C风格的for循环。

for( variable assignment; condition; iteration process)
gawk '{
> total = 0
> for (i = 1; i < 4; i++)
> {
> total += $i
> }
> avg = total / 3
> print "Average:",avg
> }' data5
Average: 128.333
Average: 137.667
Average: 176.667


六、 格式化打印

如果要创建详尽的报表,通常需要为数据选择特定的格式和位置。解决办法是使用格式化打印命令,叫作printf。如果你熟悉C语言编程的话, gawk中的printf命令用法也是一样,允许指定具体如何显示数据的指令。

下面是printf命令的格式:

printf "format string", var1, var2 ...

format string是格式化输出的关键。它会用文本元素和格式化指定符来具体指定如何呈
现格式化输出。格式化指定符是一种特殊的代码,会指明显示什么类型的变量以及如何显示。

gawk程序会将每个格式化指定符作为占位符,供命令中的变量使用。第一个格式化指定符对应列出的第一个变量,第二个对应第二个变量,依此类推。格式化指定符采用如下格式

%[modifier]control-letter

其中control-letter是一个单字符代码,用于指明显示什么类型的数据,而modifier
定义了可选的格式化特性。

因此,如果你需要显示一个字符串变量,可以用格式化指定符%s。如果你需要显示一个整数值,可以用%d%i%d是十进制数的C风格显示方式)。如果你要用科学计数法显示很大的值,就用%e格式化指定符。

gawk 'BEGIN{
> x = 10 * 100
> printf "The answer is: %e\n", x
> }'
The answer is: 1.000000e+03

除了控制字母外,还有3种修饰符可以用来进一步控制输出。

  • width:指定了输出字段最小宽度的数字值。如果输出短于这个值, printf会将文本右对齐,并用空格进行填充。如果输出比指定的宽度还要长,则按照实际的长度输出。
  • prec:这是一个数字值,指定了浮点数中小数点后面位数,或者文本字符串中显示的最大字符数。
  • -(减号):指明在向格式化空间中放入数据时采用左对齐而不是右对齐。

在使用printf语句时,你可以完全控制输出样式。

gawk 'BEGIN{FS="\n"; RS=""} {print $1,$4}' data2
Riley Mullen (312)555-1234
Frank Williams (317)555-9876
Haley Snell (313)555-4938

可以用printf命令来帮助格式化输出,使得输出信息看起来更美观。首先,让我们将print
命令转换成printf命令,看看会怎样。

gawk 'BEGIN{FS="\n"; RS=""} {printf "%s %s\n", $1, $4}' data2
Riley Mullen (312)555-1234
Frank Williams (317)555-9876
Haley Snell (313)555-4938

它会产生跟print命令相同的输出。 printf命令用%s格式化指定符来作为这两个字符串值
的占位符。注意,你需要在printf命令的末尾手动添加换行符来生成新行。没添加的话, printf命令会继续在同一行打印后续输出。

如果需要用几个单独的printf命令在同一行上打印多个输出,这就会非常有用。

gawk 'BEGIN{FS=","} {printf "%s ", $1} END{printf "\n"}' data1
data11 data21 data31

每个printf的输出都会出现在同一行上。为了终止该行, END部分打印了一个换行符。
下一步,用修饰符来格式化第一个字符串值。

gawk 'BEGIN{FS="\n"; RS=""} {printf "%16s %s\n", $1, $4}' data2
Riley Mullen (312)555-1234
Frank Williams (317)555-9876
Haley Snell (313)555-4938

通过添加一个值为16的修饰符,我们强制第一个字符串的输出宽度为16个字符。默认情况下,printf命令使用右对齐来将数据放到格式化空间中。要改成左对齐,只需给修饰符加一个减号即可。

gawk 'BEGIN{FS="\n"; RS=""} {printf "%-16s %s\n", $1, $4}' data2
Riley Mullen (312)555-1234
Frank Williams (317)555-9876
Haley Snell (313)555-4938

printf命令在处理浮点值时也非常方便。通过为变量指定一个格式,你可以让输出看起来
更统一。可以使用%5.1f格式指定符来强制printf命令将浮点值近似到小数点后一位。

gawk '{
> total = 0
> for (i = 1; i < 4; i++)
> {
> total += $i
> }
> avg = total / 3
> printf "Average: %5.1f\n",avg
> }' data5
Average: 128.3
Average: 137.7
Average: 176.7


七、 内建函数

gawk提供了不少内置函数,可进行一些常见的数学、字符串以及时间函数运算。你可以在gawk程序中利用这些函数来减少脚本中的编码工作。

1. 数学函数

如果你有过其他语言的编程经验,可能就会很熟悉在代码中使用内建函数来进行一些常见的数学运算。 gawk编程语言不会让那些寻求高级数学功能的程序员失望。

虽然数学函数的数量并不多,但gawk提供了标准数学运算中要用到的一些基本元素。

在使用一些数学函数时要小心,因为gawk语言对于它能够处理的数值有一个限定区间。如果超出了这个区间,就会得到一条错误消息。

$ gawk 'BEGIN{x=exp(100); print x}'
26881171418161356094253400435962903554686976
$ gawk 'BEGIN{x=exp(1000); print x}'
gawk: warning: exp argument 1000 is out of range
inf

第一个例子会计算e100次幂,虽然数值很大,但尚在系统的区间内。第二个例子尝试计算e1000次幂,已经超出了系统的数值区间,所以就生成了一条错误消息。

除了标准数学函数外, gawk还支持一些按位操作数据的函数,位操作函数在处理数据中的二进制值时非常有用。

  • and(v1, v2):执行值v1v2的按位与运算。
  • compl(val):执行val的补运算。
  • lshift(val, count):将值val左移count位。
  • or(v1, v2):执行值v1v2的按位或运算。
  • rshift(val, count):将值val右移count位。
  • xor(v1, v2):执行值v1v2的按位异或运算。


2. 字符串函数

gawk还提供了一些可用来处理字符串值的函数

一些字符串函数的作用相对来说显而易见。

gawk 'BEGIN{x = "testing"; print toupper(x); print length(x) }'
TESTING
7

但一些字符串函数的用法相当复杂。asortasorti函数是新加入的gawk函数,允许你基
于数据元素值(asort)或索引值(asorti)对数组变量进行排序。

gawk 'BEGIN{
> var["a"] = 1
> var["g"] = 2
> var["m"] = 3
> var["u"] = 4
> asort(var, test)
> for (i in test)
> print "Index:",i," - value:",test[i]
> }'
Index: 4 - value: 4
Index: 1 - value: 1
Index: 2 - value: 2
Index: 3 - value: 3

新数组test含有排序后的原数组的数据元素,但索引值现在变为表明正确顺序的数字值了。split函数是将数据字段放到数组中以供进一步处理的好办法。

gawk 'BEGIN{ FS=","}{
> split($0, var)
> print var[1], var[5]
> }' data1
data11 data15
data21 data25
data31 data35

新数组使用连续数字作为数组索引,从含有第一个数据字段的索引值1开始。

 

3. 时间函数

时间函数常用来处理日志文件,而日志文件则常含有需要进行比较的日期。通过将日期的文本表示形式转换成epoch时间(自1970-01-01 00:00:00 UTC到现在的秒数),可以轻松地比较日期。

gawk 'BEGIN{
> date = systime()
> day = strftime("%A, %B %d, %Y", date)
> print day
> }'
Friday, December 26, 2014

该例用systime函数从系统获取当前的epoch时间戳,然后用strftime函数将它转换成用户
可读的格式,转换过程中使用了shell命令date的日期格式化字符。
 

4. 自定义函数

除了gawk中的内建函数,还可以在gawk程序中创建自定义函数。

1)定义函数

要定义自己的函数,必须用function关键字。

function name([variables])
{
statements
}

函数名必须能够唯一标识函数。可以在调用的gawk中传给这个函数一个或多个变量。

function printthird()
{
print $3
}

这个函数会打印记录中的第三个数据字段。

函数还能用return语句返回值:return value

值可以是变量,或者是最终能计算出值的算式:

function myrand(limit)
{
return int(limit * rand())
}

你可以将函数的返回值赋给gawk程序中的一个变量:x = myrand(100)

2)使用自定义函数

在定义函数时,它必须出现在所有代码块之前(包括BEGIN代码块)。乍一看可能有点怪异,但它有助于将函数代码与gawk程序的其他部分分开。

gawk '
> function myprint()
> {
> printf "%-16s - %s\n", $1, $4
> }
> BEGIN{FS="\n"; RS=""}
> {
> myprint()
> }' data2
Riley Mullen - (312)555-1234
Frank Williams - (317)555-9876
Haley Snell - (313)555-4938

这个函数定义了myprint()函数,它会格式化记录中的第一个和第四个数据字段以供打印输出。 gawk程序然后用该函数显示出数据文件中的数据。一旦定义了函数,你就能在程序的代码中随意使用。在涉及很大的代码量时,这会省去许多工作。
 

5. 创建函数库

显而易见,每次使用函数都要重写一遍并不美妙。不过, 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程序的文件,然后在命令行上同时指定库文件
和程序文件就行了。

$ cat script4
BEGIN{ FS="\n"; RS=""}
{
myprint()
}
$ gawk -f funclib -f script4 data2
Riley Mullen - (312)555-1234
Frank Williams - (317)555-9876
Haley Snell - (313)555-4938

你要做的是当需要使用库中定义的函数时,将funclib文件加到你的gawk命令行上就可以了。


八、 实例

如果需要处理数据文件中的数据值,例如表格化销售数据或者是计算保龄球得分, gawk的一些高级特性就能派上用场。处理数据文件时,关键是要先把相关的记录放在一起,然后对相关数据执行必要的计算。

举例来说,我们手边有一个数据文件,其中包含了两支队伍(每队两名选手)的保龄球比赛
得分情况。

$ cat scores.txt
Rich Blum,team1,100,115,95
Barbara Blum,team1,110,115,100
Christine Bresnahan,team2,120,115,118
Tim Bresnahan,team2,125,112,116

每位选手都有三场比赛的成绩,这些成绩都保存在数据文件中,每位选手由位于第二列的队名来标识。下面的脚本对每队的成绩进行了排序,并计算了总分和平均分。

$ cat bowling.sh
#!/bin/bash
for team in $(gawk –F, '{print $2}' scores.txt | uniq)
do
gawk –v team=$team 'BEGIN{FS=","; total=0}
{
if ($2==team)
{
total += $3 + $4 + $5;
}
}
END {
avg = total / 6;
print "Total for", team, "is", total, ",the average is",avg
}' scores.txt
done

for循环中的第一条语句过滤出数据文件中的队名,然后使用uniq命令返回不重复的队名。for循环再对每个队进行迭代。for循环内部的gawk语句进行计算操作。对于每一条记录,首先确定队名是否和正在进行循环的队名相符。这是通过利用gawk-v选项来实现的,该选项允许我们在gawk程序中传递shell变量。如果队名相符,代码会对数据记录中的三场比赛得分求和,然后将每条记录的值再相加,只要数据记录属于同一队。在循环迭代的结尾处, gawk代码会显示出总分以及平均分。输出结果如下。

./bowling.sh
Total for team1 is 635, the average is 105.833
Total for team2 is 706, the average is 117.667

现在你就拥有了一件趁手的工具来计算保龄球锦标赛成绩了。你要做的就是将每位选手的成绩记录在文本文件中,然后运行这个脚本!

猜你喜欢

转载自blog.csdn.net/Hehuyi_In/article/details/111071437