Shell编程—gawk进阶
1使用变量
awk编程语言支持两种不同类型的变量:
- 内建变量
- 自定义变量
1.1内建变量
1. 字段和记录分隔符变量
数据字段变量允许你使用美元符号($)和字段在该记录中的位置值来引用记录对应的字段。因此,要引用记录中的第一个数据字段,就用变量$1;要引用第二个字段,就用$2,依次类推。
数据字段是由字段分隔符来划定的。默认情况下,字段分隔符是一个空白字符,也就是空格符或者制表符。
gawk的内建变量:
变 量 | 描 述 |
FIELDWIDTHS | 由空格分隔的一列数字,定义了每个数据字段确切宽度 |
FS | 输入字段分隔符 |
RS | 输入记录分隔符 |
OFS | 输出字段分隔符 |
ORS | 输出记录分隔符 |
变量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
通过设置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 $ gawk ‘BEGIN{FS=","; OFS="<-->"} {print $1,$2,$3}‘ data1 data11<-->data12<-->data13 data21<-->data22<-->data23 data31<-->data32<-->data33
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依此来解析数据记录。每个记录中的数字串会根 7 据已定义好的字段长度来分割。
变量RS和ORS定义了gawk程序如何处理数据流中的字段。默认情况下,gawk将RS和ORS设为换行符。默认的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 $ gawk ‘BEGIN{FS="\n"; RS=""} {print $1,$4}‘ data2 Riley Mullen (312)555-1234 Frank Williams (317)555-9876 Haley Snell (313)555-4938 $ cat data2 Riley Mullen 123 Main Street
对于这样的数据,可以把RS变量设置成空字符串,然后在数据记录间留一个空白行。gawk会把每个空白行当作一个记录分隔符。
2. 数据变量
变 量 | 描 述 |
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 ‘BEGIN{print ARGC,ARGV[1]}‘ data1 2 data1
ARGC变量表明命令行上有两个参数。这包括gawk命令和data1参数(记住,程序脚本并不算参数)。ARGV数组从索引0开始,代表的是命令。第一个数组值是gawk命令后的第一个命令行参数。
ENVIRON变量看起来可能有点陌生。它使用关联数组来提取shell环境变量。关联数组用文本作为数组的索引值,而不是数值。
数组索引中的文本是shell环境变量名,而数组的值则是shell环境变量的值。下面有个例子。
$ gawk ‘ > BEGIN{ > print ENVIRON["HOME"] > print ENVIRON["PATH"] > }‘ /home/rich /usr/local/bin:/bin:/usr/bin:/usr/X11R6/bin
ENVIRON["HOME"]变量从shell中提取了HOME环境变量的值。类似地,ENVIRON["PATH"]提取了PATH环境变量的值。
当要在gawk程序中跟踪数据字段和记录时,变量FNR、NF和NR用起来就非常方便。有时你并不知道记录中到底有多少个数据字段。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
NF变量含有数据文件中 后一个数据字段的数字值。可以在它前面加个美元符将其用作字段变量。
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变量则在处理第二个数据文件时继续计数。结果就是:如果只使用一个数据文件作为输入, FNR和NR的值是相同的;如果使用多个数据文件作为输入, FNR的值会在处理每个数据文件时被重置,而NR的值则会继续计数直到处理完所有的数据文件。
1.2自定义变量
1. 在脚本中给变量赋值
$ gawk ‘ > BEGIN{ > testing="This is a test" > print testing > }‘ This is a test
print语句的输出是testing变量的当前值。跟shell脚本变量一样,gawk变量可以保存数值或文本值。并且对于数字提供计算能力。
$ gawk ‘ > BEGIN{ > testing="This is a test" > print testing > testing=45 > print testing > }‘ This is a test 45
gawk编程语言包含了用来处理数字值的标准算数操作符。其中包括求余符号(%)和幂运算符号(^或**)。
$ gawk ‘BEGIN{x=4; x= x * 2 + 3; print x}‘ 11
2. 在命令行上给变量赋值
$ cat script1 BEGIN{FS=","} {print $n} $ gawk -f script1 n=2 data1 data12 data22 data32 $ gawk -f script1 n=3 data1 data13 data23 data33
使用命令行参数来定义变量值会有一个问题。在你设置了变量后,这个值在代码的BEGIN部分不可用。这时可以用-v命令行参数来解决这个问题。
$ cat script2 BEGIN{print "The starting value is",n; FS=","} {print $n} $ gawk -v n=3 -f script2 data1 The starting value is 3 data13 data23 data33
2处理数组
2.1定义数组变量
下面是一些gawk 中数组变量的例子。
capital["Illinois"] = "Springfield" capital["Indiana"] = "Indianapolis" capital["Ohio"] = "Columbus"
在引用数组变量时,必须包含索引值来提取相应的数据元素值。
$ 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.2遍历数组变量
如果要在gawk中遍历一个关联数组,可以用for语句的一种特殊形式。
for (var in array) { 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
注意,索引值不会按任何特定顺序返回,但它们都能够指向对应的数据元素值。明白这点很重要,因为你不能指望着返回的值都是有固定的顺序,只能保证索引值和数据值是对应的。
2.3删除数组变量
删除命令会从数组中删除关联索引值和相关的数据元素值。
$ 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
3使用模式
3.1正则表达式
在使用正则表达式时,正则表达式必须出现在它要控制的程序脚本的左花括号前。
$ gawk ‘BEGIN{FS=","} /11/{print $1}‘ data1 data11
3.2匹配操作符
匹配操作符(matching operator)允许将正则表达式限定在记录中的特定数据字段。匹配操作符是波浪线(~)。可以指定匹配操作符、数据字段变量以及要匹配的正则表达式。
$1 ~ /^data/
$1变量代表记录中的第一个数据字段。这个表达式会过滤出第一个字段以文本data开头的 6所有记录。下面是在gawk程序脚本中使用匹配操作符的例子。
$ gawk ‘BEGIN{FS=","} /11/{print $1}‘ data1 data11
3.3数学表达式
可以使用任何常见的数学比较表达式。
- x == y:值x等于y。
- x <= y:值x小于等于y。
- x < y:值x小于y。
- x >= y:值x大于等于y。
- x > y:值x大于y。
例子:
$ gawk -F: ‘$4 == 0{print $1}‘ /etc/passwd root sync shutdown halt operator
例子2:
$ gawk -F, ‘$1 == "data"{print $1}‘ data1 $ $ gawk -F, ‘$1 == "data11"{print $1}‘ data1 data11
4结构化命令
4.1 if语句
gawk编程语言支持标准的if-then-else格式的if语句。你必须为if语句定义一个求值的条件,并将其用圆括号括起来。如果条件求值为TRUE,紧跟在if语句后的语句会执行。如果条件求值为FALSE,这条语句就会被跳过。并且它支持else语句。
下面这个简单的例子演示了这种格式的。
$ cat data4 10 5 13 50 $ gawk ‘{ > if ($1 > 20) > { > x = $1 * 2 > print x > } else > { > x = $1 / 2 > print x > }}‘ data4 5 2.5 6.5 100 68
4.2while语句
$ 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
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
4.3do-while语句
$ gawk ‘{ > total = 0 > i = 1 > do > { > total += $i > i++ > } while (total < 150) > print total }‘ data5 250 160 315
这个脚本会读取每条记录的数据字段并将它们加在一起,直到累加结果达到150。如果第一个数据字段大于150则脚本会保证在条件被求值前至少读取第一个数据字段的内容。
4.4for语句
$ 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
5格式化打印
如果要创建详尽的报表,通常需要为数据选择特定的格式和位置。就要使用格式化打印命令,叫作printf。
下面是printf命令的格式:
printf "format string", var1, var2 . . .
gawk 程序会将每个格式化指定符作为占位符,供命令中的变量使用。第一个格式化指定符对应列出的第一个变量,第二个对应第二个变量,依此类推。
格式化指定符采用如下格式:
%[modifier]control-letter
其中control-letter是一个单字符代码,用于指明显示什么类型的数据,而modifier则定义了可选的格式化特性。下面列出了可用在格式化指定符中的控制字母:
控制字母 | 描 述 |
c | 将一个数作为ASCII字符显示 |
d | 显示一个整数值 |
i | 显示一个整数值(跟d一样) |
e | 用科学计数法显示一个数 |
f | 显示一个浮点值 |
g | 用科学计数法或浮点数显示(选择较短的格式) |
o | 显示一个八进制值 |
s | 显示一个文本字符串 |
x | 显示一个十六进制值 |
X | 显示一个十六进制值,但用大写字母A~F |
$ gawk ‘BEGIN{ > x = 10 * 100 > printf "The answer is: %e\n", x > }‘ The answer is: 1.000000e+03
除了控制字母外,还有3种修饰符可以用来进一步控制输出。
- width:指定了输出字段 小宽度的数字值。如果输出短于这个值,printf会将文本右对齐,并用空格进行填充。如果输出比指定的宽度还要长,则按照实际的长度输出。
- prec:这是一个数字值,指定了浮点数中小数点后面位数,或者文本字符串中显示的大字符数。
- -(减号):指明在向格式化空间中放入数据时采用左对齐而不是右对齐。在使用printf语句时,你可以完全控制输出样式。
我们用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
通过添加一个值为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
6内建函数
6.1数学函数
下面列出了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) | 为计算随机数指定一个种子值 |
int() 函数会生成一个值的整数部分,但它并不会四舍五入取近似值。它的做法更像其他编程语言中的 floor函数。它会生成该值和0之间 接近该值的整数。这意味着int()函数在值为5.6时返回5,在值为-5.6时则返回-5。
rand()函数非常适合创建随机数,但这个随机数只在0和1之间(不包括0或1)
x = int(10 * rand()) #产生0-9的整数
在使用一些数学函数时要小心,因为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
除了标准数学函数外,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.2字符串函数
- 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, variables) :用提供的format和variables返回一个类似于printf输出的字符串
- sub(r, s [,t]) :在变量$0或目标字符串t中查找正则表达式r的匹配。如果找到了,就用字符串s替换掉第一处匹配
- substr(s, i [,n]) :返回s中从索引值i开始的n个字符组成的子字符串。如果未提供n,则返回s剩下的部分
- tolower(s) :将s中的所有字符转换成小写
- toupper(s) :将s中的所有字符转换成大写
asort和asorti函数是新加入的gawk函数,允许你基
于数据元素值(asort)或索引值(asorti)对数组变量进行排序。这里有个使用asort的例子。
$ 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含有排序后的原数组的数据元素,但索引值现在变为表明正确顺序的数字值了。
6.3时间函数
函 数 | 描 述 |
mktime(datespec) | 将一个按YYYY MM DD HH MM SS [DST]格式指定的日期转换成时间戳值① |
strftime(format [,timestamp]) | 将当前时间的时间戳或timestamp(如果提供了的话)转化格式化日期(采用shell 函数date()的格式) |
systime( ) | 返回当前时间的时间戳 |
下面是在gawk程序中使用时间函数的例子。
$ gawk ‘BEGIN{ > date = systime() > day = strftime("%A, %B %d, %Y", date) > print day > }‘ Friday, December 26, 2014
7自定义函数
7.1定义函数
要定义自己的函数,必须用function关键字。
function name([variables]) { statements }
函数名必须能够唯一标识函数。可以在调用的gawk程序中传给这个函数一个或多个变量。
函数还能用return语句返回值:return value
值可以是变量,或者是 终能计算出值的算式:
function myrand(limit) { return int(limit * rand()) }
你可以将函数的返回值赋给gawk程序中的一个变量:x = myrand(100)
7.2使用自定义函数
在定义函数时,它必须出现在所有代码块之前(包括BEGIN代码块)。
$ 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
7.3创建函数库
$ cat funclib function myprint() { printf "%-16s - %s\n", $1, $4 } function myrand(limit) { return int(limit * rand()) } function printthird() { print $3 } $ 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
8实例
举例来说,我们手边有一个数据文件,其中包含了两支队伍(每队两名选手)的保龄球比赛得分情况。
$ 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
现在你就拥有了一件趁手的工具来计算保龄球锦标赛成绩了。你要做的就是将每位选手的成绩记录在文本文件中,然后运行这个脚本。