gawk程序提供了一种编程语言而不只是编辑器命令,在gawk编程语言中,你可以做下面的事情:
- 定义变量来保存数据;
- 使用算术和字符串操作符来处理数据;
- 使用结构化编程概念(比如if-then语句和循环)来为数据处理增加处理逻辑;
- 通过提取数据文件中的数据元素,将其重新排列或格式化,生成格式化报告。
gawk options program file
-F fs | 指定行中划分数据字段的字段分隔符 |
-f file | 从指定的文件中读取程序 |
-v var=value | 定义gawk程序中的变量及其默认值 |
-mf N | 指定要处理的数据文件中最大字段数 |
-mr N | 指定数据文件中的最大数据行数 |
-w keyword | 指定gawk的兼容模式或警告等级 |
命令行操作
-
从命令行读取程序脚本
gawk程序脚本用一对花括号来定义。你必须将脚本命令放到两个花括号({})中。由于gawk命令行假定脚本是单个文本字符串,你还必须将脚本放到单引号中。
$ gawk '{print "Hello World!"}' #没有在命令行上指定文件名,所以gawk程序会从STDIN接收数据
>This is a test
Hello World!
>hello
Hello World!
>This is another test
Hello World!
要终止这个gawk程序,你必须表明数据流已经结束了。bash shell提供了一个组合键来生成EOF(End-of-File)字符。Ctrl+D组合键会在bash中产生一个EOF字符。这个组合键能够终止该gawk程序并返回到命令行界面提示符下。
-
使用数据字段变量
gawk会将如下变量分配给它在文本行中发现的数据字段:在文本行中,每个数据字段都是通过字段分隔符划分的。gawk在读取一行文本时,会用预定义的字段分隔符划分每个数据字段。gawk中默认的字段分隔符是任意的空白字符(例如空格或制
表符)。
$0代表整个文本行;
$1代表文本行中的第1个数据字段;
$2代表文本行中的第2个数据字段;
$n代表文本行中的第n个数据字段。
$ 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
$
$ gawk -F: '{print $1}' /etc/passwd #用冒号来分隔数字字段
-
在程序脚本中使用多个命令
gawk编程语言允许你将多条命令组合成一个正常的程序。要在命令行上的程序脚本中使用多条命令,只要在命令之间放个分号即可。
$ echo "My name is Rich" | gawk '{$4="Christine"; print $0}'#第一条命令会给字段变量$4赋值。第二条命令会打印整个数据字段
My name is Christine
$
也可以用次提示符一次一行地输入程序脚本命令。
$ gawk '{
> $4="Christine"
> print $0}'
My name is Rich
My name is Christine
$
-
从文件中读取程序
$ cat script2.gawk
{print $1 "'s home directory is " $6}
$
$ gawk -F: -f script2.gawk /etc/passwd
-
BEGIN 和 END
BEGIN会强制gawk在读取数据前执行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
Line 2
Line 3
End of File
$
使用变量
gawk编程语言支持两种不同类型的变量:内建变量; 自定义变量。gawk有一些内建变量。这些变量存放用来处理数据文件中的数据字段和记录的信息。你也可以在gawk程序里创建你自己的变量。
-
内建变量
字段和记录分隔符变量
数据字段变量允许你使用美元符号($)和字段在该记录中的位置值来引用记录对应的字段。默认情况下,字段分隔符是一个空白字符,也就是空格符或者制表符 。我们可以在命令行下使用命令行参数-F或者在gawk程序中使用特殊的内建变量FS来更改字段分隔符。
FIELDWIDTHS | 由空格分隔一列数字,定义每个数据字段的确切宽度 |
FS | 输入字段分隔符 |
RS | 输入记录分割符 |
OFS | 输出字段分隔符 |
ORS | 输出记录分隔符 |
变量FS和OFS定义了gawk如何处理数据流中的数据字段。你变量OFS具备相同的功能,只不过是用在print命令的输出上。print命令会自动将OFS变量的值放置在输出中的每个字段间
print $1,$2,$3
# field1 field2 field3
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
$
默认情况下,gawk将RS和ORS设为换行符。默认的RS值表明,输入数据流中的每行新文本就是一条新纪录。你会在数据流中碰到占据多行的字段。典型的例子是包含地址和电话号码的数据,其中地址和电话号码各占一行。
如果你用默认的FS和RS变量值来读取这组数据,gawk就会把每行作为一条单独的记录来读取,并将记录中的空格当作字段分隔符。要解决这个问题,只需把FS变量设置成换行符,把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
$
数据变量
gawk还提供了一些内建数据变量来帮助你了解数据发生了什么变化,并提取shell环境的信息。
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并不会将程序脚本当成命令行参数的一部分。ARGC变量表明命令行上有两个参数。这包括gawk命令和data1参数(记住,程序脚本并不算参数)。ARGV数组从索引0开始,代表的是命令。第一个数组值是gawk命令后的第一个命令行参数。
$ gawk 'BEGIN{print ARGC,ARGV[1]}' data1
2 data1
$
ENVIRON变量使用关联数组来提取shell环境变量。关联数组用文本作为数组的索引值,而不是数值。数组索引中的文本是shell环境变量名,而数组的值则是shell环境变量的值。
$ gawk '
> BEGIN{
> print ENVIRON["HOME"]
> print ENVIRON["PATH"]
> }'
/home/rich
/usr/local/bin:/bin:/usr/bin:/usr/X11R6/bin
$
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变量则含有已处理过的记录总数。
$ 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 #当gawk程序处理第二个数据文件时,FNR值被设回了1;NR的值则会继续计数
data21 FNR=2 NR=5
data31 FNR=3 NR=6
There were 6 records processed
$
-
自定义变量
gawk自定义变量名可以是任意数目的字母、数字和下划线,但不能以数字开头。重要的是,要记住gawk变量名区分大小写。
在脚本中给变量赋值
gawk编程语言包含了用来处理数字值的标准算数操作符。其中包括求余符号(%)和幂运算符号(^或**)。
$ gawk 'BEGIN{x=4; x= x * 2 + 3; print x}'
11
$
在命令行上给变量赋值
也可以用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
$
使用命令行参数来定义变量值会有一个问题。在你设置了变量后,这个值在代码的BEGIN部分不可用。可以用-v命令行参数来解决这个问题。它允许你在BEGIN代码之前设定变量。在命令行上,-v命令行参数必须放在脚本代码之前。
$ 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
$
$ gawk -v n=3 -f script2 data1
The starting value is 3
data13
data23
data33
$
处理数组
gawk编程语言使用关联数组提供数组功能 。关联数组跟数字数组不同之处在于它的索引值可以是任意文本字符串。你不需要用连续的数字来标识数组中的数据元素。相反,关联数组用各种字符串来引用值,和字典是同一个概念。
-
定义数组变量
var[index] = element
capital["Illinois"] = "Springfield"
capital["Indiana"] = "Indianapolis"
capital["Ohio"] = "Columbus"
$ gawk 'BEGIN{
> var[1] = 34
> var[2] = 3
> total = var[1] + var[2]
> print total
> }'
37
$
-
遍历数组变量
如果要在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
$
-
删除数组变量
delete array[index]
使用模式
gawk程序支持多种类型的匹配模式来过滤数据记录,这一点跟sed编辑器大同小异。
-
正则表达式
在使用正则表达式时,正则表达式必须出现在它要控制的程序脚本的左花括号前。
$ gawk 'BEGIN{FS=","} /11/{print $1}' data1
data11
$
-
匹配操作符
匹配操作符允许将正则表达式限定在记录中的特定数据字段。匹配操作符是波浪线(~)。可以指定匹配操作符、数据字段变量以及要匹配的正则表达式。$1变量代表记录中的第一个数据字段。这个表达式会过滤出第一个字段以文本data开头的所有记录。
$1 ~ /^data/
$ gawk 'BEGIN{FS=","} $2 ~ /^data2/{print $0}' data1
data21,data22,data23,data24,data25
$
$ gawk -F: '$1 ~ /rich/{print $1,$NF}' /etc/passwd
rich /bin/bash
$
也可以用!符号来排除正则表达式的匹配。如果记录中没有找到匹配正则表达式的文本,程序脚本就会作用到记录数据。
$ gawk –F: '$1 !~ /rich/{print $1,$NF}' /etc/passwd
root /bin/bash
daemon /bin/sh
bin /bin/sh
sys /bin/sh
--- output truncated ---
$
-
数学表达式
匹配数据字段中的数字值时非常方便。举个例子,如果你想显示所有属于root用户组(组ID为0)的系统用户,可以用这个脚本。
$ gawk -F: '$4 == 0{print $1}' /etc/passwd
root
sync
shutdown
halt
operator
$
x == y:值x等于y。
x <= y:值x小于等于y。
x < y:值x小于y。
x >= y:值x大于等于y。
x > y:值x大于y。
结构化命令
-
if 语句
if (condition)
statement1
# 也可以将它放在一行上,像这样:
if (condition) statement1
$ gawk '{if ($1 > 20) print $1}' data4
$ gawk '{
> if ($1 > 20)
> {
> x = $1 * 2
> print x
> }
> }' data4
gawk的if语句也支持else子句,允许在if语句条件不成立的情况下执行一条或多条语句。
$ gawk '{
> if ($1 > 20)
> {
> x = $1 * 2
> print x
> } else
> {
> x = $1 / 2
> print x
> }}' data4
$ gawk '{if ($1 > 20) print $1 * 2; else print $1 / 2}' data4
-
while 语句
while (condition)
{
statements
}
$ gawk '{
> total = 0
> i = 1
> while (i < 4)
> {
> total += $i
> i++
> }
> avg = total / 3
> print "Average:",avg
> }' data5
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
-
do-while 语句
do
{
statements
} while (condition)
$ gawk '{
> total = 0
> i = 1
> do
> {
> total += $i
> i++
> } while (total < 150)
> print total }' data5
-
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
格式化打印
print语句在显示数据上只是控制输出字段分隔符(OFS)。如果要创建详尽的报表,使用格式化打印printf命令
printf "format string", var1, var2 . . .
format string是格式化输出的关键。它会用文本元素和格式化指定符来具体指定如何呈现格式化输出。格式化指定符采用如下格式:control-letter是一个单字符代码,用于指明显示什么类型的数据,而modifier则定义了可选的格式化特性
%[modifier]control-letter
c | 将一个数作为ASCII字符显示 |
d或i | 显示一个整数值 |
e | 用科学计数法显示一个数 |
f | 显示一个浮点值 |
g | 用科学计数法或浮点数显示 |
o | 显示一个八进制值 |
s | 显示一个文本字符串 |
x | 显示一个十六进制值 |
X | 显示一个十六进制值,但用大写字母 |
$ gawk 'BEGIN{
> x = 10 * 100
> printf "The answer is: %e\n", x
> }'
The answer is: 1.000000e+03
$
除了控制字母外,还有3种修饰符可以用来进一步控制输出
width:指定了输出字段最小宽度的数字值。如果输出短于这个值,printf会将文本右
对齐,并用空格进行填充。如果输出比指定的宽度还要长,则按照实际的长度输出。
prec:这是一个数字值,指定了浮点数中小数点后面位数,或者文本字符串中显示的最
大字符数。
-(减号):指明在向格式化空间中放入数据时采用左对齐而不是右对齐。
内建函数
gawk编程语言提供了不少内置函数,可进行一些常见的数学、字符串以及时间函数运算。
-
数学函数
atan2(x,y) | x/y的反正切,x和y以弧度为单位 |
cos(x) | x的余弦 |
exp(x) | x的指数函数 |
int(x) | 取整(类似floor()) |
log(x) | 自然对数 |
rand() | 比0大比1小的随机浮点数 |
sin(x) | 正弦 |
sqrt(x) | 平方根 |
srand(x) | 为计算随机数指定一个种子值 |
除了标准数学函数外,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的按位异或运算。
-
字符串函数
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)对数组变量进行排序
$ 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
$
split函数是将数据字段放到数组中以供进一步处理的好办法
$ gawk 'BEGIN{ FS=","}{
> split($0, var)
> print var[1], var[5]
> }' data1
data11 data15
data21 data25
data31 data35
$
-
时间函数
mktime(datespec) | 将一个按YYYY MM DD HH MM SS [DST]格式指定的日期转换成时间戳值① |
strftime(format[,timestamp]) | 将当前时间的时间戳或timestamp转化格式化日期(采用shell函数date()的格式) |
systime( ) | 返回当前时间的时间戳 |
$ gawk 'BEGIN{
> date = systime()
> day = strftime("%A, %B %d, %Y", date)
> print day
> }'
Friday, December 26, 2014
$
自定义函数
要定义自己的函数,必须用function关键字。
function name([variables])
{
statements
}
function myrand(limit)
{
return int(limit * rand())
}
在定义函数时,它必须出现在所有代码块之前(包括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
$
显而易见,每次使用函数都要重写一遍并不美妙。不过,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
$
实例
如果需要处理数据文件中的数据值,例如表格化销售数据或者是计算保龄球得分,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
$