gawk是一门功能丰富的编程语言,你可以通过它所提供的各种特性来编写好几程序处理数据。
22.1 使用变量
gawk编程语言支持两种不同类型的变量:
内建变量和自定义变量
22.1.1 内建变量
gawk程序使用内建变量来引用程序数据里的一些特殊功能
1.字段和记录分隔符变量
数据字段变量:允许你使用美元符和字段在该记录中的位置值来引用记录对应的字段。
要引用第一个字段就用变量$1,第二个就用$2,….以此类推。
数据字段是由分隔符来划定的。默认字段分隔符是一个空白字符,也就是空格或者制表符。
有一组内建变量用于控制gawk如何处理输入输出数据中的字段和记录,见下表:
变量 | 描述 |
FIELDWIDTHS | 有空格分隔的一列数字,定义每个数据字段的确切宽度 |
FS | 输入字段分隔符 |
RS | 输入记录分隔符 |
OFS | 输出字段分隔符 |
ORS | 输出记录分隔符 |
1)print命令会自动将OFS变量的值放置在输出中的每个字段间。
实例:
2) FIELDWIDTHS变量允许你不依靠字段分割符来读取记录。一旦这是了FILEDWIDTFS变量,gawk就会忽略FS变量。
警告:一旦设定了FIELDWIDTHS变量的值,就不能再改变了。这种方法并不适用于变长的字段
有写数据没有指定分隔符,而是放在特定的列,这时候就可以用FIELDWIDTHS了:
例子:
3)RS和ORS定义了gawk程序如何处理数据流中的字段。默认这两个都是换行符
默认的RS表明,输入数据流中的每行新文本就是一条新记录
例子:
上面的例子中,4行才是一条记录,所以指定FS=”\n”
每行只是一个字段。
如何判断一个新的数据行的开始:解决方法计算RS变量设为空。然后在数据记录之间留一个空白行。gawk会把每个空白行当做一个记录分隔符。
说明:
默认的字段分隔符是空格,记录分割符是换行符
上面的例子把字段分割符改成了换行符,记录分隔符编程了空白行(RS=””)
2. 数据变量
还有一些其他的内建变量:
变量 | 描述 |
ARGC | 当前命令行参数个数 |
ARGIND | 当前文件在ARGV的位置 |
ARGV | 包含命令行参数的数组 |
CONVFMT | 数字的转换格式,模式是%.6 g |
ENVIRON | 当前shell环境变量及其值组成的关联数组 |
ERRNO | 当读取或关闭文件发生错误时的系统错误号 |
FILENAME | 用作输入数据的数据文件的文件名 |
FNR | 当前数据文件的数据行数 |
IGNORECASE | 设成非零值,忽略gawk命令中出现的字符串的字符大小写 |
NF | 数据文件中的字段总数 |
NR | 已处理的输入记录数 |
OFMT | 数字的输出格式,默认值%.6 g |
RLENGTH | 由match函数所匹配的字符串的长度 |
RSTART | 由match函数所匹配的字符串的起始位置 |
实例1:
ENVIRON[“HOME”] 从系统中提取HOME环境变量的值。
例子2:
当要在gawk程序中跟踪数据字段和记录时,变量FNR,NF和NR就非常方便了。
NF变量可以在你不知道具体位置的情况下指定记录中的最后一个数据字段:
$gawk ‘BEGIN{FS=”:”; OFS=”:”} {print $1, $NF}’ /etc/passwd
假设NF为7,那么相当于是$7。打印最后一个字段
例子3:
FNR变量含有当前数据文件中已处理过的记录数
NR变量则含有已处理过的记录总数
当处理第2个文件时,FNR又被置成1了,但是NR还是继续增加的。
注意:
1)在shell脚本中使用gawk时,应该将gawk的命令放到不同的行,便于理解和阅读
2)如果在不同的shell脚本中使用了相同的gawk脚本,应该把gawk放在一个单独的文件中。再用-f参数去引用它。
22.1.2自定义变量
变量名可以是字母下划线开头,还可以有数字。并且变量名区分大小写
1.在脚本中给变量赋值
可以对变量进行修改,可以进行数学运算
例子:
2. 在命令行上给变量赋值
也可以用gawk命令行来给程序中的变量赋值。这允许你在正常的代码之外赋值。
上面可以给n进行赋值,改变脚本的行为。
这样可以在不改变脚本代码的情况下就能改变脚本的行为
上面这样存在的问题是设置的变量在代码的BEGIN部分不可用
解决方法,用-v参数。它允许你在BEGIN代码之前设定变量,要放在脚本代码之前。
22.2 处理数组
gawk编程语言使用关联数组提供数组功能
关联数组跟数字数组不同之处在于它的索引值可以是任意文本字符串。
不需要用连续的数字来标识数组元素。关联数组用各种字符串来引用值
每个索引字符串都必须能够唯一标识赋给它的数据元素
22.2.1 定义数组变量
用标准赋值语句来定义数组变量。格式如下:
var[index]=element
var是变量名,index是关联数组的索引值 element是数据元素值
例子:
这里要加双引号,数字不用加,字符串需要加
22.2.2 遍历数组变量
关联数组的索引可以是任何东西
遍历数组可以用for语句的一种特殊形式:
for (var in array)
{
statements
}
这个for语句会在每次循环时都将关联数组array的下一个索引值赋值给变量var,然后执行一遍statements
22.2.3删除数组变量
格式如下:
delete array[index]
删除以后就没办法再用它来提取元素值了。
比如:
22.3 使用模式
gawk支持多种类型的匹配模式来过滤数据记录。
BEGIN和END关键字用来读取数据流之前或之后执行命令的特殊模式
22.3.1 正则表达式
可以用基础正则表达式(BRE)或扩展正则表达式(ERE)来选择程序脚本作用在数据流中的哪些行上。
使用正则表达式时,正则表达式必须出现在它要控制的程序脚本的左花括号前。
正则表达式/11/匹配了字段中含有字符串11的记录。
22.3.2 匹配操作符
匹配操作符允许将正则表达式限定在记录中的特定数据字段。匹配操作符是~。
可以指定匹配操作符,数据字段变量以及要匹配的正则表达式
$1 ~ /^data/
$1变量代表记录中的第一个数据字段。
上面的例子会过滤出以data开头的所有记录。
取反: $1 !~ /^data1/ 匹配第一个字段不以data1开头的记录
例子2:
// 匹配第2个字段为data2开头的记录,并且打印第1和第3个字段。$2表示第2个字段
例子3:
例子4:! 用来排除正则表达式中的匹配
-F 用来指定主句字段的分隔符
上面表明过滤第一个字段不以xcy开头,或不以root开头。
22.3.3 数学表达式
还可以在匹配模式中用数学表达式。
例子:想显示所有属于root用户组(组ID为0)的系统用户
$gawk –F: ‘$4 == 0{print $1}’ /etc/passwd
还可以用任何常见的数学比较表达式: == <= >= > <
匹配字符串:注意这时候是完全匹配
$gawk –F, ‘$1==”data” {print $1}’ data1
第一个字段必须是data,而不是包含data
22.4 结构化命令
22.4.1 if语句
给if语句定义一个求值的条件,并将其用圆括号括起来。
条件为真在if后面的语句就会执行。
还可以接上else。和C语言的差不多
例子:
还可以在单行上使用else子句,这样就需要在if后面接上分号;
$ gawk '{if($1 == 3) print $1" == 3 "; else print $1,"!= 3"}' data4
22.4.2 while 语句
基本格式:
while (condition)
{
statement
}
while里面还可以放break和continue。用起来跟C语言一样
例子:
22.4.3 do-while语句
和while语句类似,但是会在检查条件语句之前执行命令。格式如下:
do
{
statement
} while(condition)
这种格式保证了语句在条件被求值之前至少执行一次
例子:
22.4.4 for语句
支持C风格的for循环:
例子:
22.5 格式化打印
print打印在如何显示数据上并未提供多少控制。
下面介绍一个格式化打印命令,printf,和C语言的那个有点类似:
printf “format string” ,var1,var2…
前面也是格式化命令。跟C语言很像:
1)
%c 输出字符, %d 整数值, %i 整数值,%e 用科学计数法显示数
%f 浮点数,%g 科学计数法或浮点数显示(较短的)
%o 八进制,%s 字符串
%x 十六进制小写,%X 十六进制大写
2)
还有三种修饰符可以用来进一步控制输出
width:指定输出字段最小宽度的数字值。实际比这短,则会补充空格,否则按正常输出
prec:指定浮点数中小数点后面的位数。或者文本字符串中显示的最大字符数
-(减号):指明在格式化空间中放入数据时采用左对齐,而不是右对齐
例子:
还可以指定浮点数格式
… {printf “%5.1f\n”, avg} …
占5位,小数点后只显示一位。
22.6 内建函数
gawk提供了不少内建的函数,可以进行常见的数学 字符串以及时间函数运算
22.6.1 数学函数
函数 | 描述 |
atan2(x,y) | x/y的反正切,x y以弧度为单位 |
cos(x) | X的余弦 x以弧度为单位 |
exp(x) | X的指数函数 |
int(x) | X的整数部分,取靠近零一侧的值 |
log(x) | X的自然对数 |
rand(x) | 比0大比1小的随机浮点数 |
sin(x) | 正弦,x以弧度为单位 |
sqrt(x) | X的平方根 |
srand(x) | 为计算随机数指定一个种子值 |
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的异或运算 |
例子:
22.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(若提供的话)来匹配正则表达式。 如果找到了就全部替换成字符串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中查找正则表达式t的匹配。若找到了,就用字符串s替换掉第一处匹配 |
substr[s,i [,n]] | 返回s从索引值i开始的n个字符组成的字符串。若未提供n,则返回s剩下的部分 |
tolower(s) | 全部转小写 |
toupper(s) | 全部转大写 |
有些用起来比较简单,比如大小写,求长度
下面是asort的例子:
注意看对var数组的数据元素进行排序了。排序后的数组放在test数组里面了。
索引值被替换成了数字。索引最大的对应数据元素也是最大的。
下面是split的例子:
将每一行用FS字符(,)分开,放到了test数组上。再打印数组的数据。
count表示字段总数
22.6.3 时间函数
函数 | 描述 |
mktime(datadpace) | 将一个按YYYYMMDDHHMMSS[DST]格式指定的日期转成时间戳值 |
strftime(format [,timestamp]) | 将当前时间的时间戳或timestamp(若提供的话)转化格式化日期(采用shell函数data()的格式) |
systime() | 返回当前时间的时间戳 |
例子:
注意:BEGIN后面的{要挨着BEGIN写,不能换行写。
否则报错
xcy@xcy-virtual-machine:~/shell/22zhang$ gawk -f script11
gawk: script11:2: BEGIN 块必须有一个行为部分
22.7 自定义函数
22.7.1 定义函数
必须要用function关键字,格式如下:
function name([variables])
{
statement
}
函数名必须能够统一标识函数。可以在调用的gawk程序中传给这个函数一个或多个变量
例子:
// 打印记录中的第三个字段
function printthird()
{
print $3
}
还可以用return返回值。
例子:
function myrand(limit)
{
return int(limit * rand())
}
用法:
x=myrand(100)
22.7.2 使用自定义函数
定义函数时,它必须出现在所有代码块之前(包括BEGIN代码块)。
实例:
先格式化记录中的第一个和第四个数据字段。再输出
定义了函数就可以在程序的代码中随便使用了
22.7.3 创建函数库
可以将多个函数放到一个库文件中,这样就能在所有的gawk程序中使用了。
步骤:
1)先创建一个存储所有gawk函数的文件
2)就可以在脚本中使用啦
要引用文件需要使用-f参数。可以在同一命令行中使用多个-f参数。
22.8 实例
假设有一个数据文件,里面有两支队伍每队2个人,每人3次的比赛成绩。要求总成绩和平均成绩:
下面是脚本:
脚本分析:
1)注意uniq这个关键字,这里可以排除一样的。for语句是用来筛选队名的。
2)for循环里面,假如队名是team1,那么就先处理team1。会读取所有记录,将队名都为team1的记录的$3 $4 $5相加,就是总成绩了。最后求平均值
这里是运行情况: