继 : Linux shell 脚本编程-高级篇 (五)
6. gawk 进阶
本节将进一步深入了解如何定制 gawk。gawk 是一门功能丰富的编程语言,可以通过它所提供的各种特性来编写高级程序处理数据。
6.1 使用变量
所有编程语言共有的一个重要特性是使用变量来存取值。gawk编程语言支持两种不同类型的变量:
□ 内建变量
□ 自定义变量
gawk 有一些内建变量。这些变量存放用来处理数据文件中的数据字段和记录的信息。用户也可以在 gawk 程序里创建自己的变量。
6.1.1 内建变量
-----------------------------------------------------------------------------------------------------------------------------------------
gawk 程序使用内建变量来引用程序数据里的一些特殊功能。
■ 字段和记录分隔符变量
-----------------------------------------------------------------------------------------------------------------------------------------
数据字段变量允许使用美元符号($)和字段在该记录中的位置值来引用记录对应的字段。要引用记录中的第一个数据字段,就用变量 $1;引用第二个字段,
就用 $2,依次类推。
数据字段是由字段分隔符来划定的。默认情况下,字段分隔符是一个空白字符,也就是空格符或者制表符。在命令行下使用命令行参数 -F 或者在 gawk 程序
中使用特殊的内建变量 FS 来更改字段分隔符。
内建变量 FS 是一组内建变量中的一个,这组变量用于控制 gawk 如何处理输入输出数据中的字段和记录。下表列出了这些内建变量:
gawk 数据字段和记录变量
+---------------+-------------------------------------------------------------------------
| 变 量 | 描 述
+---------------+-------------------------------------------------------------------------
| FIELDWIDTHS | 由空格分隔的一列数字,定义了每个数据字段确切宽度
+---------------+-------------------------------------------------------------------------
| FS | 输入字段分隔符
+---------------+-------------------------------------------------------------------------
| RS | 输入记录分隔符
+---------------+-------------------------------------------------------------------------
| OFS | 输出字段分隔符
+---------------+-------------------------------------------------------------------------
| ORS | 输出记录分隔符
+---------------+-------------------------------------------------------------------------
变量 FS 和 OFS 定义了 gawk 如何处理数据流中的数据字段。我们已经知道了如何使用变量 FS 来定义记录中的字段分隔符。变量 OFS 具备相同的功能,
只不过是用在 print 命令的输出上。
默认情况下,gawk 将 OFS 设成一个空格,所以如果用命令:
print $1,$2,$3
会看到如下输出:
field1 field2 field3
示例:
[devalone@devalone 22]$ cat data1
data11,data12,data13,data14,data15
data21,data22,data23,data24,data25
data31,data32,data33,data34,data35
[devalone@devalone 22]$ gawk 'BEGIN{FS=","} {print $1,$2,$3}' data1
data11 data12 data13
data21 data22 data23
data31 data32 data33
print 命令会自动将 OFS 变量的值放置在输出中的每个字段间。通过设置 OFS 变量,可以在输出中使用任意字符串来分隔字段。
示例:
[devalone@devalone 22]$ gawk 'BEGIN{FS=",";OFS="-"} {print $1,$2,$3}' data1
data11-data12-data13
data21-data22-data23
data31-data32-data33
[devalone@devalone 22]$ gawk 'BEGIN{FS=",";OFS="--"} {print $1,$2,$3}' data1
data11--data12--data13
data21--data22--data23
data31--data32--data33
[devalone@devalone 22]$ gawk 'BEGIN{FS=",";OFS="<-->"} {print $1,$2,$3}' data1
data11<-->data12<-->data13
data21<-->data22<-->data23
data31<-->data32<-->data33
FIELDWIDTHS 变量允许不依靠字段分隔符来读取记录。在一些应用程序中,数据并没有使用字段分隔符,而是被放置在了记录中的特定列。这种情况下,必须
设定 FIELDWIDTHS 变量来匹配数据在记录中的位置。
一旦设置了 FIELDWIDTH 变量,gawk 就会忽略 FS 变量,并根据提供的字段宽度来计算字段。下面是个采用字段宽度而非字段分隔符的例子:
[devalone@devalone 22]$ cat data1b
1005.3247596.37
115-2.349194.00
05810.1298100.1
[devalone@devalone 22]$ gawk 'BEGIN{FIELDWIDTHS="3 5 2 5"}{print $1,$2,$3}' data1b
100 5.324 75
115 -2.34 91
058 10.12 98
FIELDWIDTHS 变量定义了四个字段,gawk 依此来解析数据记录。每个记录中的数字串会根据已定义好的字段长度来分割。
NOTE:
-------------------------------------------------------------------------------------------------------------------------------------
一定要记住,一旦设定了 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 会把每个空白行当作一个记录分隔符。
示例:
[devalone@devalone 22]$ 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
[devalone@devalone 22]$ 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 把文件中的每行都当成一个字段,把空白行当作记录分隔符。
■ 数据变量
-----------------------------------------------------------------------------------------------------------------------------------------
除了字段和记录分隔符变量外,gawk 还提供了其他一些内建变量来帮助了解数据发生了什么变化,并提取 shell 环境的信息。下表列出了 gawk 中的其他
内建变量:
更多的 gawk 内建变量
+---------------+--------------------------------------------------------------------------
| 变 量 | 描 述
+---------------+--------------------------------------------------------------------------
| ARGC | 当前命令行参数个数
+---------------+--------------------------------------------------------------------------
| ARGIND | 当前文件在ARGV中的位置
+---------------+--------------------------------------------------------------------------
| ARGV | 包含命令行参数的数组
+---------------+--------------------------------------------------------------------------
| CONVFMT | 数字的转换格式(参见printf语句),默认值为%.6 g
+---------------+--------------------------------------------------------------------------
| ENVIRON | 当前shell环境变量及其值组成的关联数组
+---------------+--------------------------------------------------------------------------
| ERRNO | 当读取或关闭输入文件发生错误时的系统错误号
+---------------+--------------------------------------------------------------------------
| FILENAME | 用作gawk输入数据的数据文件的文件名
+---------------+--------------------------------------------------------------------------
| FNR | 当前数据文件中的数据行数
+---------------+--------------------------------------------------------------------------
| IGNORECASE | 设成非零值时,忽略gawk命令中出现的字符串的字符大小写
+---------------+--------------------------------------------------------------------------
| NF | 数据记录中的字段总数
+---------------+--------------------------------------------------------------------------
| NR | 已处理的输入记录数
+---------------+--------------------------------------------------------------------------
| OFMT | 数字的输出格式,默认值为%.6 g
+---------------+--------------------------------------------------------------------------
| RLENGTH | 由 match 函数所匹配的子字符串的长度
+---------------+--------------------------------------------------------------------------
| RSTART | 由 match 函数所匹配的子字符串的起始位置
+---------------+--------------------------------------------------------------------------
ARGC 和 ARGV 变量允许从 shell 中获得命令行参数的总数以及它们的值。但这可能有点麻烦,因为 gawk 并不会将程序脚本当成命令行参数的一部分。
如:
[devalone@devalone 22]$ gawk 'BEGIN{print ARGC,ARGV[0]}' data1
2 gawk
[devalone@devalone 22]$ gawk 'BEGIN{print ARGC,ARGV[1]}' data1
2 data1
ARGC 变量表明命令行上有两个参数。这包括 gawk 命令和 data1参 数(记住,程序脚本并不算参数)。ARGV 数组从索引 0 开始,代表的是命令。第一个
数组值是 gawk 命令后的第一个命令行参数。
NOTE:
-------------------------------------------------------------------------------------------------------------------------------------
跟 shell 变量不同,在脚本中引用 gawk 变量时,变量名前不加美元符。
ENVIRON 变量使用关联数组来提取 shell 环境变量。关联数组用文本作为数组的索引值,而不是数值。
数组索引中的文本是 shell 环境变量名,而数组的值则是 shell 环境变量的值。
示例:
[devalone@devalone 22]$ gawk '
> BEGIN{
> print ENVIRON["HOME"]
> print ENVIRON["PATH"]
> }'
/home/devalone
/usr/local/protoc/bin:/home/devalone/programs/apache-tomcat-8.5.11/bin:/usr/local/apache/maven/bin:/usr/local/apache/ant/bin
ENVIRON["HOME"] 变量从 shell 中提取了 HOME 环境变量的值。类似地,ENVIRON["PATH"] 提取了 PATH 环境变量的值。可以用这种方法来从 shell 中
提取任何环境变量的值,以供 gawk 程序使用。
当要在 gawk 程序中跟踪数据字段和记录时,变量 FNR、NF 和 NR 用起来就非常方便。有时并不知道记录中到底有多少个数据字段。NF 变量可以在不知道
具体位置的情况下指定记录中的最后一个数据字段。
示例:
[devalone@devalone 22]$ gawk 'BEGIN{FS=":";OFS=":"} {print $1, $NF}' /etc/passwd
root:/bin/bash
bin:/sbin/nologin
daemon:/sbin/nologin
adm:/sbin/nologin
lp:/sbin/nologin
sync:/bin/sync
shutdown:/sbin/shutdown
...
devalone:/bin/bash
NF 变量含有数据记录中最后一个数据字段的数字值。可以在它前面加个美元符将其用作字段变量。
FNR 和 NR 变量虽然类似,但又略有不同。FNR 变量含有当前数据文件中已处理过的记录数,NR 变量则含有已处理过的记录总数。
示例:
[devalone@devalone 22]$ gawk 'BEGIN{FS=","}{print $1, "FNR="FNR}' data1 data1
data11 FNR=1
data21 FNR=2
data31 FNR=3
data11 FNR=1
data21 FNR=2
data31 FNR=3
在这个例子中,gawk 程序的命令行定义了两个输入文件(两次指定的是同样的输入文件)。这个脚本会打印第一个数据字段的值和 FNR 变量的当前值。
注意,当 gawk 程序处理第二个数据文件时,FNR 值被设回了 1。
现在,加上 NR 变量看看会输出什么:
[devalone@devalone 22]$ 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 变量则在处理第二个数据文件时继续计数。结果就是:如果只使用一个数据文件作为输入,
FNR 和 NR的值是相同的;如果使用多个数据文件作为输入,FNR 的值会在处理每个数据文件时被重置,而 NR 的值则会继续计数直到处理完所有的数据文件。
6.1.2 自定义变量
-----------------------------------------------------------------------------------------------------------------------------------------
跟其他典型的编程语言一样,gawk 允许用户定义自己的变量在程序代码中使用。gawk 自定义变量名可以是任意数目的字母、数字和下划线,但不能以数字
开头。gawk 变量名区分大小写。
■ 在脚本中给变量赋值
-----------------------------------------------------------------------------------------------------------------------------------------
在 gawk 程序中给变量赋值跟在 shell 脚本中赋值类似,都用赋值语句:
[devalone@devalone 22]$ gawk '
> BEGIN{
> testing="This is a test"
> print testing
> }'
This is a test
print 语句的输出是 testing 变量的当前值。跟 shell 脚本变量一样,gawk 变量可以保存数值或文本值。
示例:
[devalone@devalone 22]$ gawk '
> BEGIN{
> testing="this is a test"
> print testing
> testing=45
> print testing
> }'
this is a test
45
在这个例子中,testing 变量的值从文本值变成了数值。
赋值语句还可以包含数学算式来处理数字值:
[devalone@devalone 22]$ gawk 'BEGIN {x=4;x=x * 2 + 3; print x}'
11
gawk 编程语言包含了用来处理数字值的标准算数操作符。其中包括求余符号(%)和幂运算符号(^或**)。
■ 在命令行上给变量赋值
-----------------------------------------------------------------------------------------------------------------------------------------
也可以用 gawk 命令行来给程序中的变量赋值。这允许在正常的代码之外赋值,即时改变变量的值。下面的例子使用命令行变量来显示文件中特定数据字段。
示例:
[devalone@devalone 22]$ cat script1.gawk
BEGIN{FS=","}
{print $n}
[devalone@devalone 22]$ gawk -f script1.gawk n=2 data1
data12
data22
data32
[devalone@devalone 22]$ gawk -f script1.gawk n=3 data1
data13
data23
data33
这个特性可以在不改变脚本代码的情况下就能够改变脚本的行为。第一个例子显示了文件的第二个数据字段,第二个例子显示了第三个数据字段,只要在命令
行上设置 n 变量的值就行。
使用命令行参数来定义变量值会有一个问题。在设置了变量后,这个值在代码的 BEGIN 部分不可用。
示例:
[devalone@devalone 22]$ cat script2.gawk
BEGIN{print "The starting value is",n; FS=","}
{print $n}
[devalone@devalone 22]$ gawk -f script2.gawk n=3 data1
The starting value is
data13
data23
data33
可以用 -v 命令行参数来解决这个问题。它允许在 BEGIN 代码之前设定变量。在命令行上,-v 命令行参数必须放在脚本代码之前。
示例:
[devalone@devalone 22]$ gawk -v n=3 -f script2.gawk data1
The starting value is 3
data13
data23
data33
现在在 BEGIN 代码部分中的变量 n 的值已经是命令行上设定的那个值了。
6.2 处理数组
-----------------------------------------------------------------------------------------------------------------------------------------
gawk 编程语言使用关联数组提供数组功能。关联数组跟数字数组不同之处在于它的索引值可以是任意文本字符串。不需要用连续的数字来标识数组中的数据
元素。相反,关联数组用各种字符串来引用值。每个索引字符串都必须能够唯一地标识出赋给它的数据元素。
6.2.1 定义数组变量
-----------------------------------------------------------------------------------------------------------------------------------------
可以用标准赋值语句来定义数组变量。数组变量赋值的格式如下:
var[index] = element
其中 var 是变量名,index 是关联数组的索引值,element 是数据元素值。下面是一些 gawk 中数组变量的例子:
capital["Illinois"] = "Springfield"
capital["Indiana"] = "Indianapolis"
capital["Ohio"] = "Columbus"
在引用数组变量时,必须包含索引值来提取相应的数据元素值。
示例:
[devalone@devalone 22]$ gawk 'BEGIN{
> capital["Illinois"] = "Springfield"
> print capital["Illinois"]
> }'
Springfield
在引用数组变量时,会得到数据元素的值。数据元素值是数字值时也一样。
示例:
[devalone@devalone 22]$ gawk 'BEGIN{
> var[1] = 34
> var[2] = 3
> total = var[1] + var[2]
> print total
> }'
37
可以像使用 gawk 程序中的其他变量一样使用数组变量。
6.2.2 遍历数组变量
-----------------------------------------------------------------------------------------------------------------------------------------
关联数组变量的问题在于可能无法知晓索引值是什么。跟使用连续数字作为索引值的数字数组不同,关联数组的索引可以是任何东西。
如果要在 gawk 中遍历一个关联数组,可以用 for 语句的一种特殊形式:
for (var in array)
{
statements
}
这个 for 语句会在每次循环时将关联数组 array 的下一个索引值赋给变量 var,然后执行一遍 statements。重要的是记住这个变量中存储的是索引值而
不是数组元素值。可以将这个变量用作数组的索引,轻松地取出数据元素值。
示例:
[devalone@devalone 22]$ gawk 'BEGIN{
> var["a"] = 1
> var["b"] = 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: b - Value: 2
注意,索引值不会按任何特定顺序返回,但它们都能够指向对应的数据元素值。明白这点很重要,因为不能指望返回的值都是有固定的顺序,只能保证索引值
和数据值是对应的。
6.2.3 删除数组变量
-----------------------------------------------------------------------------------------------------------------------------------------
从关联数组中删除数组索引要用一个特殊的命令:
delete array[index]
删除命令会从数组中删除关联索引值和相关的数据元素值。
示例:
[devalone@devalone 22]$ 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
一旦从关联数组中删除了索引值,就没法再用它来提取元素值。
6.3 使用模式
-----------------------------------------------------------------------------------------------------------------------------------------
gawk 程序支持多种类型的匹配模式来过滤数据记录,这一点跟 sed 编辑器大同小异。之前已经介绍了两种特殊的模式在实践中的应用。BEGIN 和 END 关键
字是用来在读取数据流之前或之后执行命令的特殊模式。类似地,也可以创建其他模式在数据流中出现匹配数据时执行一些命令。
6.3.1 正则表达式
-----------------------------------------------------------------------------------------------------------------------------------------
可以用基础正则表达式(BRE)或扩展正则表达式(ERE)来选择程序脚本作用在数据流中的哪些行上。
在使用正则表达式时,正则表达式必须出现在它要控制的程序脚本的左花括号前。
示例:
[devalone@devalone 22]$ cat data1
data11,data12,data13,data14,data15
data21,data22,data23,data24,data25
data31,data32,data33,data34,data35
[devalone@devalone 22]$ gawk 'BEGIN{FS=","} /11/{print $1}' data1
data11
正则表达式 /11/ 匹配了数据字段中含有字符串11的记录。gawk 程序会用正则表达式对记录中所有的数据字段进行匹配,包括字段分隔符。
示例:
[devalone@devalone 22]$ gawk 'BEGIN{FS=","} /,d/{print $1}' data1
data11
data21
data31
这个例子使用正则表达式匹配了用作字段分隔符的逗号。这也并不总是件好事。它可能会造成如下问题:当试图匹配某个数据字段中的特定数据时,这些
数据又出现在其他数据字段中。如果需要用正则表达式匹配某个特定的数据实例,应该使用匹配操作符。
6.3.2 匹配操作符
-----------------------------------------------------------------------------------------------------------------------------------------
匹配操作符(matching operator)允许将正则表达式限定在记录中的特定数据字段。匹配操作符是波浪线(~)。可以指定匹配操作符、数据字段变量以及
要匹配的正则表达式,如:
$1 ~ /^data/
$1 变量代表记录中的第一个数据字段。这个表达式会过滤出第一个字段以文本 data 开头的所有记录。
示例:
[devalone@devalone 22]$ gawk 'BEGIN{FS=","} $2 ~ /^data2/{print $0}' data1
data21,data22,data23,data24,data25
匹配操作符会用正则表达式 /^data2/ 来比较第二个数据字段,该正则表达式指明字符串要以文本 data2 开头。
这是件强大的工具,gawk 程序脚本中经常用它在数据文件中搜索特定的数据元素。
示例:
[devalone@devalone 22]$ gawk -F: '$1 ~ /devalone/{print $1, $NF}' /etc/passwd
devalone /bin/bash
这个例子会在第一个数据字段中查找文本 devalon。如果在记录中找到了这个模式,它会打印该记录的第一个和最后一个数据字段值。
也可以用 ! 符号来排除正则表达式的匹配:
$1 !~ /expression/
如果记录中没有找到匹配正则表达式的文本,程序脚本就会作用到记录数据。
示例:
[devalone@devalone 22]$ gawk -F: '$1 !~ /devalone/{print $1, $NF}' /etc/passwd
root /bin/bash
bin /sbin/nologin
daemon /sbin/nologin
adm /sbin/nologin
lp /sbin/nologin
sync /bin/sync
shutdown /sbin/shutdown
halt /sbin/halt
mail /sbin/nologin
...
这个例子中,gawk 程序脚本打印 /etc/passwd 文件中与用户ID devalone 不匹配的用户ID和登录 shell。
6.3.3 数学表达式
-----------------------------------------------------------------------------------------------------------------------------------------
除了正则表达式,也可以在匹配模式中用数学表达式。这个功能在匹配数据字段中的数字值时非常方便。
示例: 显示所有属于root用户组(组ID为0)的系统用户
[devalone@devalone 22]$ gawk -F: '$4 == 0{print $1}' /etc/passwd
root
sync
shutdown
halt
operator
这段脚本会查看第四个数据字段含有值 0 的记录。在这个 Linux 系统中,有五个用户账户属于 root 用户组。
可以使用任何常见的数学比较表达式:
□ x == y:值x等于y。
□ x <= y:值x小于等于y。
□ x < y:值x小于y。
□ x >= y:值x大于等于y。
□ x > y:值x大于y。
也可以对文本数据使用表达式,但必须小心。跟正则表达式不同,表达式必须完全匹配。数据必须跟模式严格匹配。
示例:
[devalone@devalone 22]$ gawk -F, '$1 == "data"{print $1}' data1
[devalone@devalone 22]$
[devalone@devalone 22]$ gawk -F, '$1 == "data11"{print $1}' data1
data11
[devalone@devalone 22]$
第一个测试没有匹配任何记录,因为第一个数据字段的值不在任何记录中。第二个测试用值 data11 匹配了一条记录。
6.4 结构化命令
-----------------------------------------------------------------------------------------------------------------------------------------
gawk 编程语言支持常见的结构化编程命令。
6.4.1 if 语句
-----------------------------------------------------------------------------------------------------------------------------------------
gawk 编程语言支持标准的 if-then-else 格式的 if 语句。必须为 if 语句定义一个求值的条件,并将其用圆括号括起来。如果条件求值为 TRUE,紧跟在
if 语句后的语句会执行。如果条件求值为 FALSE,这条语句就会被跳过。可以用这种格式:
if (condition)
statement1
也可以将它放在一行上,像这样:
if (condition) statement1
示例:
[devalone@devalone 22]$ gawk '{if ($1 > 20) print $1}' data4
50
34
如果需要在 if 语句中执行多条语句,就必须用花括号将它们括起来。
示例:
[devalone@devalone 22]$ gawk '{
> if ($1 > 20)
> {
> x = $1 * 2
> print x
> }
> }' data4
100
68
注意,不能弄混 if 语句的花括号和用来表示程序脚本开始和结束的花括号。如果弄混了,gawk 程序能够发现丢失了花括号,并产生一条错误消息。
gawk 的 if 语句也支持 else 子句,允许在 if 语句条件不成立的情况下执行一条或多条语句。
示例:
[devalone@devalone 22]$ 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
示例:
[devalone@devalone 22]$ gawk '{if ($1 > 20) print $1 * 2; else print $1/2}' data4
5
2.5
6.5
100
68
这个格式更紧凑,但也更难理解。
6.4.2 while 语句
-----------------------------------------------------------------------------------------------------------------------------------------
while 语句为 gawk 程序提供了一个基本的循环功能。下面是 while 语句的格式:
while (condition)
{
statements
}
while 循环允许遍历一组数据,并检查迭代的结束条件。如果在计算中必须使用每条记录中的多个数据值,这个功能能帮得上忙。
示例:
[devalone@devalone 22]$ cat data5
130 120 135
160 113 140
145 170 215
[devalone@devalone 22]$ 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语句,允许从循环中跳出。
示例:
[devalone@devalone 22]$ 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
6.4.3 do-while 语句
-----------------------------------------------------------------------------------------------------------------------------------------
do-while 语句类似于 while 语句,但会在检查条件语句之前执行命令。下面是 do-while 语句的格式:
do
{
statements
} while (condition)
这种格式保证了语句会在条件被求值之前至少执行一次。当需要在求值条件前执行语句时,这个特性非常方便。
示例:
[devalone@devalone 22]$ gawk '{
> total = 0
> i = 1
> do
> {
> total += $i
> i++
> } while (total < 150)
> print total }' data5
250
160
315
这个脚本会读取每条记录的数据字段并将它们加在一起,直到累加结果达到 150。如果第一个数据字段大于150(就像在第二条记录中看到的那样),则脚本
会保证在条件被求值前至少读取第一个数据字段的内容。
6.4.4 for 语句
-----------------------------------------------------------------------------------------------------------------------------------------
for 语句是许多编程语言执行循环的常见方法。gawk 编程语言支持 C 风格的 for 循环:
for( variable assignment; condition; iteration process)
示例:
[devalone@devalone 22]$ gawk '{
> total = 0
> for (i=0; i<4; i++)
> {
> total += $i
> }
> avg = total / 3
> print "Average:",avg
> }' data5
Average: 171.667
Average: 191
Average: 225
定义了 for 循环中的迭代计数器,就不用担心要像使用 while 语句一样自己负责给计数器增值了。
6.5 格式化打印
-----------------------------------------------------------------------------------------------------------------------------------------
print 语句在 gawk 如何显示数据上并未提供多少控制。能做的只是控制输出字段分隔符(OFS)。如果要创建详尽的报表,通常需要为数据选择特定的格式
和位置。
解决办法是使用格式化打印命令,叫作 printf。如果熟悉 C 语言编程的话,gawk 中的 printf 命令用法也是一样,允许指定具体如何显示数据的指令。
printf 命令的格式:
printf "format string", var1, var2 . . .
format string 是格式化输出的关键。它会用文本元素和格式化指定符来具体指定如何呈现格式化输出。格式化指定符是一种特殊的代码,会指明显示什么
类型的变量以及如何显示。gawk 程序会将每个格式化指定符作为占位符,供命令中的变量使用。第一个格式化指定符对应列出的第一个变量,第二个对应第
二个变量,依此类推。
格式化指定符采用如下格式:
%[modifier]control-letter
其中 control-letter 是一个单字符代码,用于指明显示什么类型的数据,而 modifier 则定义了可选的格式化特性。下表列出了可用在格式化指定符中的
控制字母。
格式化指定符的控制字母
+-----------+------------------------------------------------------------------------------
| 控制字母 | 描 述
+-----------+------------------------------------------------------------------------------
| c | 将一个数作为ASCII字符显示
+-----------+------------------------------------------------------------------------------
| d | 显示一个整数值
+-----------+------------------------------------------------------------------------------
| i | 显示一个整数值(跟d一样)
+-----------+------------------------------------------------------------------------------
| e | 用科学计数法显示一个数
+-----------+------------------------------------------------------------------------------
| f | 显示一个浮点值
+-----------+------------------------------------------------------------------------------
| g | 用科学计数法或浮点数显示(选择较短的格式)
+-----------+------------------------------------------------------------------------------
| o | 显示一个八进制值
+-----------+------------------------------------------------------------------------------
| s | 显示一个文本字符串
+-----------+------------------------------------------------------------------------------
| x | 显示一个十六进制值
+-----------+------------------------------------------------------------------------------
| X | 显示一个十六进制值,但用大写字母A~F
+-----------+------------------------------------------------------------------------------
因此,如果需要显示一个字符串变量,可以用格式化指定符 %s。如果需要显示一个整数值,可以用 %d或 %i(%d 是十进制数的 C风格显示方式)。如果要
用科学计数法显示很大的值,就用 %e 格式化指定符。
示例:
[devalone@devalone 22]$ gawk 'BEGIN{
> x = 10 * 100
> printf "The answer is: %e\n",x
> }'
The answer is: 1.000000e+03
除了控制字母外,还有 3 种修饰符可以用来进一步控制输出:
□ width:指定了输出字段最小宽度的数字值。如果输出短于这个值,printf 会将文本右对齐,并用空格进行填充。如果输出比指定的宽度还要长,
则按照实际的长度输出。
□ prec:这是一个数字值,指定了浮点数中小数点后面位数,或者文本字符串中显示的最大字符数。
□ -(减号):指明在向格式化空间中放入数据时采用左对齐而不是右对齐。
在使用 printf 语句时,可以完全控制输出样式。
示例:
[devalone@devalone 22]$ 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 命令来帮助格式化输出,使得输出信息看起来更美观:
[devalone@devalone 22]$ 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
注意,需要在 printf 命令的末尾手动添加换行符来生成新行。没添加的话,printf 命令会继续在同一行打印后续输出。如果需要用几个单独的 printf
命令在同一行上打印多个输出,这就会非常有用。
示例:
[devalone@devalone 22]$ gawk 'BEGIN{FS=","} {printf "%s ", $1} END{printf "\n"}' data1
data11 data21 data31
每个 printf 的输出都会出现在同一行上。为了终止该行,END 部分打印了一个换行符。
下一步,用修饰符来格式化第一个字符串值:
[devalone@devalone 22]$ 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 命令使用右对齐来将数据放到格式化空间中。要改成
左对齐,只需给修饰符加一个减号即可:
[devalone@devalone 22]$ 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 命令在处理浮点值时也非常方便。通过为变量指定一个格式,可以让输出看起来更统一。
示例:
[devalone@devalone 22]$ 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
可以使用 %5.1f 格式指定符来强制 printf 命令将浮点值近似到小数点后一位。
6.6 内建函数
-----------------------------------------------------------------------------------------------------------------------------------------
gawk 编程语言提供了不少内置函数,可进行一些常见的数学、字符串以及时间函数运算。可以在 gawk 程序中利用这些函数来减少脚本中的编码工作。
6.6.1 数学函数
-----------------------------------------------------------------------------------------------------------------------------------------
下表列出了 gawk 中内建的数学函数。
gawk 数学函数
+---------------+---------------------------------------------------------------------
| 函 数 | 描 述
+---------------+---------------------------------------------------------------------
| atan2(x, y) | x/y的反正切,x和y以弧度为单位
+---------------+---------------------------------------------------------------------
| cos(x) | x的余弦,x以弧度为单位
+---------------+---------------------------------------------------------------------
| exp(x) | x的指数函数
+---------------+---------------------------------------------------------------------
| int(x) | x的整数部分,取靠近零一侧的值
+---------------+---------------------------------------------------------------------
| log(x) | x的自然对数
+---------------+---------------------------------------------------------------------
| rand() | 比0大比1小的随机浮点值
+---------------+---------------------------------------------------------------------
| sin(x) | x的正弦,x以弧度为单位
+---------------+---------------------------------------------------------------------
| sqrt(x) | x的平方根
+---------------+---------------------------------------------------------------------
| srand(x) | 为计算随机数指定一个种子值
+---------------+---------------------------------------------------------------------
虽然数学函数的数量并不多,但 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 语言对于它能够处理的数值有一个限定区间。如果超出了这个区间,就会得到一条错误消息。
示例:
[devalone@devalone 22]$ gawk 'BEGIN{x = exp(100); print x}'
26881171418161356094253400435962903554686976
[devalone@devalone 22]$ gawk 'BEGIN{x = exp(1000); print x}'
gawk: cmd. line:1: warning: exp: argument 1000 is out of range
inf
第一个例子会计算 e 的 100次幂,虽然数值很大,但尚在系统的区间内。第二个例子尝试计算e 的 1000 次幂,已经超出了系统的数值区间,所以就生成了
一条错误消息。
除了标准数学函数外,gawk 还支持一些按位操作数据的函数:
□ and(v1, v2) :执行值 v1和 v2的按位与运算。
□ compl(val) :执行val的补运算。
□ lshift(val, count):将值 val左移 count位。
□ or(v1, v2) :执行值 v1和 v2的按位或运算。
□ rshift(val, count):将值 val右移 count位。
□ xor(v1, v2) :执行值 v1和 v2的按位异或运算。
位操作函数在处理数据中的二进制值时非常有用。
6.6.2 字符串函数
-----------------------------------------------------------------------------------------------------------------------------------------
gawk 编程语言还提供了一些可用来处理字符串值的函数:
gawk 字符串函数
+-----------------------+-----------------------------------------------------------------------------------------------+
| 函 数 | 描 述 |
+-----------------------+-----------------------------------------------------------------------------------------------+
| asort(s [,d]) | 将数组 s 按数据元素值排序。索引值会被替换成表示新的排序顺序的连续数字。另外,如果指定了 d, |
| | 则排序后的数组会存储在数组 d 中。 |
+-----------------------+-----------------------------------------------------------------------------------------------+
| asorti(s [,d]) | 将数组s按索引值排序。生成的数组会将索引值作为数据元素值,用连续数字索引来表明排序顺序。 |
| | 另外如果指定了d,排序后的数组会存储在数组d中 |
+-----------------------+-----------------------------------------------------------------------------------------------+
| gensub(r, s, h [, t]) | 查找变量$0或目标字符串t(如果提供了的话)来匹配正则表达式r。如果h是一个以g或G开头的字符串, |
| | 就用s替换掉匹配的文本。如果h是一个数字,它表示要替换掉第 h处 r匹配的地方 |
+-----------------------+-----------------------------------------------------------------------------------------------+
| gsub(r, s [,t]) | 查找变量$0或目标字符串t(如果提供了的话)来匹配正则表达式r。如果找到了,就全部替换成字符串s |
+-----------------------+-----------------------------------------------------------------------------------------------+
| index(s, t) | 返回字符串t在字符串s中的索引值,如果没找到的话返回0 |
+-----------------------+-----------------------------------------------------------------------------------------------+
| length([s]) | 返回字符串s的长度;如果没有指定的话,返回$0的长度 |
+-----------------------+-----------------------------------------------------------------------------------------------+
| match(s, r [,a]) | 返回字符串s中正则表达式r出现位置的索引。如果指定了数组a,它会存储s中匹配正则表达式的那部分 |
+-----------------------+-----------------------------------------------------------------------------------------------+
| split(s, a [,r]) | 将 s 用 FS 字符或正则表达式 r(如果指定了的话)分开放到数组 a 中。返回字段的总数 |
+-----------------------+-----------------------------------------------------------------------------------------------+
| sprintf(format, | 用提供的 format和 variables返回一个类似于 printf输出的字符串 |
| variables) | |
+-----------------------+-----------------------------------------------------------------------------------------------+
| sub(r, s [,t]) | 在变量$0或目标字符串t中查找正则表达式r的匹配。如果找到了,就用字符串s替换掉第一处匹配 |
+-----------------------+-----------------------------------------------------------------------------------------------+
| substr(s, i [,n]) | 返回s中从索引值i开始的n个字符组成的子字符串。如果未提供n,则返回 s剩下的部分 |
+-----------------------+-----------------------------------------------------------------------------------------------+
| tolower(s) | 将 s中的所有字符转换成小写 |
+-----------------------+-----------------------------------------------------------------------------------------------+
| toupper(s) | 将 s中的所有字符转换成大写 |
+-----------------------+-----------------------------------------------------------------------------------------------+
示例:
[devalone@devalone 22]$ gawk 'BEGIN{x = "testing"; print toupper(x); print length(x) }'
TESTING
7
但一些字符串函数的用法相当复杂。asort 和 asorti 函数是新加入的 gawk函数,允许基于数据元素值(asort)或索引值(asorti)对数组变量进行排序。
示例:
[devalone@devalone 22]$ 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: 1 - value: 1
Index: 2 - value: 2
Index: 3 - value: 3
Index: 4 - value: 4
新数组 test 含有排序后的原数组的数据元素,但索引值现在变为表明正确顺序的数字值了。
split 函数是将数据字段放到数组中以供进一步处理的好办法。
示例:
[devalone@devalone 22]$ gawk 'BEGIN{ FS=","} {
> split($0,var)
> print var[1], var[5]
> }' data1
data11 data15
data21 data25
data31 data35
新数组使用连续数字作为数组索引,从含有第一个数据字段的索引值 1 开始。
6.6.3 时间函数
-----------------------------------------------------------------------------------------------------------------------------------------
gawk 编程语言包含一些函数来帮助处理时间值,如下表所示:
gawk 的时间函数
+-------------------------------+---------------------------------------------------------------------------------
| 函 数 | 描 述
+-------------------------------+---------------------------------------------------------------------------------
| mktime(datespec) | 将一个按YYYY MM DD HH MM SS [DST]格式指定的日期转换成时间戳值
+-------------------------------+---------------------------------------------------------------------------------
| strftime(format [,timestamp]) | 将当前时间的时间戳或timestamp(如果提供了的话)转化格式化日期(采用shell
| | 函数date()的格式)
+-------------------------------+---------------------------------------------------------------------------------
| systime( ) | 返回当前时间的时间戳
+-------------------------------+---------------------------------------------------------------------------------
时间函数常用来处理日志文件,而日志文件则常含有需要进行比较的日期。通过将日期的文本表示形式转换成 epoch时间(自1970-01-01 00:00:00 UTC 到
现在的秒数),可以轻松地比较日期。
示例:
[devalone@devalone 22]$ gawk 'BEGIN{
> date = systime()
> day = strftime("%A,%B %d,%Y",date)
> print day
> }'
星期五,七月 13,2018
该例用 systime 函数从系统获取当前的 epoch 时间戳,然后用 strftime 函数将它转换成用户可读的格式,转换过程中使用了 shell 命令 date 的日期
格式化字符。
6.7 自定义函数
-----------------------------------------------------------------------------------------------------------------------------------------
除了 gawk 中的内建函数,还可以在 gawk 程序中创建自定义函数。
6.7.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)
这个变量包含函数的返回值。
6.7.2 使用自定义函数
-----------------------------------------------------------------------------------------------------------------------------------------
在定义函数时,它必须出现在所有代码块之前(包括 BEGIN 代码块)。乍一看可能有点怪异,但它有助于将函数代码与 gawk 程序的其他部分分开。
示例:
[devalone@devalone 22]$ 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 程序然后用该函数显示出数据文件中的数据。
6.7.3 创建函数库
-----------------------------------------------------------------------------------------------------------------------------------------
每次使用函数都要重写一遍并不美妙。gawk 提供了一种途径来将多个函数放到一个库文件中,这样就能在所有的 gawk 程序中使用了。
首先,需要创建一个存储所有 gawk 函数的文件。
[devalone@devalone 22]$ 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 程序的文件,然后在命令行上同时指定库文件和程序文件就行了。
示例:
[devalone@devalone 22]$ cat script4.gawk
BEGIN{ FS="\n"; RS=""}
{
myprint()
}
[devalone@devalone 22]$ gawk -f funclib -f script4.gawk data2
Riley Mullen - (312)555-1234
Frank Williams - (317)555-9876
Haley Snell - (313)555-4938
要做的是当需要使用库中定义的函数时,将 funclib 文件加到 gawk 命令行上就可以了。
系列目录:
Linux shell 脚本编程-高级篇 (一)
Linux shell 脚本编程-高级篇 (二)
Linux shell 脚本编程-高级篇 (三)
Linux shell 脚本编程-高级篇 (四)
Linux shell 脚本编程-高级篇 (五)
Linux shell 脚本编程-高级篇 (六)
Linux shell 脚本编程-高级篇 (七)
-----------------------------------------------------------------------------------------------------------------------------------------
参考:
《Linux 命令行与 shell 脚本编程大全》 第 3 版 —— 2016.8(美)Richard Blum Cristine Bresnahan