gawk 是最初 Unix 系统上 awk 程序的 GNU 版本。相对于作为流式编辑器的 sed 而言,它提供了更为强大的编程语言特性。
其功能与特性包括:
- 定义变量来存储数据
- 通过代数运算符和字符串操作符来处理数据
- 使用结构化编程语句如 if-then 和循环等
- 从数据文件中提取出有价值的字段再重新组合以生成结构化的报表
基本语法
gawk options program file
构成 gawk 脚本的语句须包含在一对大括号( {} )中,而作为命令选项的整个脚本需要包含在一对引号中:1
2
3
4$ cat test.txt
Hello World!
$ gawk '{print $0}' test.txt
Hello World!
1. 使用字段变量
gawk 会自动地将每行文本中的每个数据字段赋值给一个指定的变量,默认情况下,预先定义的变量为:
- $0 表示一整行文本
- $1 表示该行文本的第一个字段
- $2 表示该行文本的第二个字段
- $n 表示该行文本的第 n 个字段
文本行中的数据字段是通过预先定义的字段分隔符来分隔开的,默认为空格(包括 TAB )1
2
3
4
5
6
7
8$ cat data.txt
One line of test text.
Two lines of test text.
Three lines of test text.
$ gawk '{print $1}' data.txt
One
Two
Three
可以通过 -F 选项指定另外的分隔符(如 ‘ , ‘ )
1
2
3
4
5
6
7
8 $ cat user.csv
Jack,male,20
Rose,female,18
Mike,male,24
$ gawk -F, '{print $1}' user.csv
Jack
Rose
Mike
2. 多个命令
gawk 语言允许在脚本语句中组合多个命令使用,只需要在各命令之间使用分号( ; )分隔开即可1
2$ echo "My name is Rich" | gawk '{$4="Christine"; print $0}'
My name is Christine
也可以这样:1
2
3
4
5$ gawk '{
> $4="Christine"
> print $0}'
My name is Rich
My name is Christine
其中 My name is Rich 是运行时程序获取的用户输入,My name is Christine 是程序运行后的输出
3. BEGIN & END
默认情况下,gawk 从输入中读取一行文本,再对该文本执行程序指令。而有时候需要在读取待处理数据之前先执行某些指令,此时就要用到BEGIN关键字。
同样的,END 关键字允许你指定在数据处理完成后才执行的脚本。1
2
3
4
5
6
7
8$ gawk 'BEGIN {print "The data File Contents:"}
> {print $0}
> END {print "End of File"}' data.txt
The data File Contents:
One line of test text.
Two lines of test text.
Three lines of test text.
End of File
4. 从文件中获取脚本
gawk 允许先将其程序脚本保存在某个文件中,再通过 -f 选项指定该文件的文件名。而在脚本文件中,各命令不再需要通过 ‘;’ 符号分隔,直接分行列出即可。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19$ cat script.gawk
BEGIN {
print "Users and their age"
print "User \t Age"
FS=","
}
{
print $1 " \t " $3
}
END {
print "There are three people"
}
$ gawk -f script.gawk user.csv
Users and their age
User Age
Jack 20
Rose 18
Mike 24
There are three people
上述脚本文件中的 FS=”,” 用于定义字段分隔符,效果等同于 -F 选项。
高级特性
1. 变量
程序语言最重要的特性之一就是定义和引用变量。gawk 语言支持两种类型的变量:内建变量和用户自定义变量。
gawk 程序在处理文本数据时,一次只读取一小段文本,称为 Record 。默认的 Record 分隔符即为换行符。而每条 Record 又可进一步划分成字段(Data Field),并按顺序依次赋值给 $1,$2,$n
等。默认的字段分隔符为空格(包括 TAB)
控制数据字段和 Record 的内建变量:
变量名 | 描述 |
---|---|
FIELDWIDTHS | 用一串由空格分隔的数字定义每个数据字段的具体宽度 |
FS | 定义数据字段分割符(输入) |
RS | 定义 Record 分割符(输入) |
OFS | 定义数据字段分割符(输出) |
ORS | 定义 Record 分割符(输出) |
默认的 OFS 为空格1
2
3
4
5
6
7
8$ gawk 'BEGIN{FS=","} {print $1,$2,$3}' user.csv
Jack male 20
Rose female 18
Mike male 24
$ gawk 'BEGIN{FS=",";OFS=":"} {print $1,$2,$3}' user.csv
Jack:male:20
Rose:female:18
Mike:male:24
当定义了 FIELDWIDTHS 变量时,gawk 在读取数据时就会忽略字段分割符(FS),转而使用字段宽度来分割数据。1
2
3
4
5
6
7
8$ cat numbers.txt
1005.3247596.37
11522.349194.00
05810.1298100.1
$ gawk 'BEGIN{FIELDWIDTHS="3 5 2 5"} {print $1,$2,$3,$4}' numbers.txt
100 5.324 75 96.37
115 22.34 91 94.00
058 10.12 98 100.1
有些时候会遇到如下组织方式的文本文件:1
2
3
4
5
6
7
8
9
10$ cat people.txt
Riley Mullen
123 Main Street
Chicago, IL 60601
(312)555-1234
Frank Williams
456 Oak Street
Indianapolis, IN 46201
(317)555-9876
此时可将字段分隔符(FS)设置为 “\n”,Record 分隔符(RS)设置为空字符串,则 gawk 程序会将空行作为一条 Record 的终止点。1
2
3$ gawk 'BEGIN{FS="\n"; RS=""} {print $1,$4}' people.txt
Riley Mullen (312)555-1234
Frank Williams (317)555-9876
其他内建变量
变量 | 描述 |
---|---|
ARGC | 当前命令行参数的数目 |
ARGV | 由命令行参数组成的数组 |
CONVFMT | 数字的转换格式,默认值为 %.6 g |
ENVIRON | 包含当前系统环境变量的关联数组(字典) |
ERRNO | 当读取或关闭文件出现错误时返回的系统错误 |
FILENAME | gawk 处理的数据文件的文件名 |
FNR | 当前正在处理的 Record 序号 |
IGNORECASE | 设置为非零值时忽略大小写 |
NF | 数据文件中的字段序号 |
NR | 已处理的 Record 总数 |
OFMT | 数字的输出格式,默认为 %.6 g |
1 | $ gawk 'BEGIN{FS=","; |
ARGV 的索引是从 0 开始的,表示第一个命令行参数(呃,所以通常就是 gawk 这个命令本身)。程序脚本(引号中的内容)不算在参数内。
用户自定义变量
- 在脚本中定义变量
1 | $ gawk ' |
- 在命令行参数中定义变量
1 | $ cat script1.gawk |
2. 数组
定义数组:var[index]=element
1
2
3
4
5
6
7$ gawk 'BEGIN{
> var[1] = 34
> var[2] = 3
> total = var[1] + var[2]
> print total
> }'
37
遍历数组:1
2
3
4for (var in array)
{
statements
}
1 | $ gawk 'BEGIN{ |
关联数组遍历的顺序是随机的
3. 模式匹配
正则表达式
1 | $ cat user.csv |
匹配符(~)
匹配符(~)用来对 Record 中的特定字段使用正则表达式。!~ 表示不匹配。1
2
3
4
5
6
7
8
9
10
11$ cat data
This is line 1
Another line
line three
This is line four
$ gawk '$3 ~ /line/{print $0}' data
This is line 1
This is line four
$ gawk '$3 !~ /line/{print $0}' data
Another line
line three
数学表达式
1 | $ cat user.csv |
4. 结构化命令
if
if (condition) statement
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21$ cat numbers
2
4
6
8
10
$ gawk '{
> if ($1 < 5)
> {
> x = $1 - 2
> print x
> } else
> {
> x = $1 / 2
> print x
> }}' numbers
0
2
3
4
5
while
1 | while (condition) |
1 | cat number |
for
for( variable assignment; condition; iteration process)
1
2
3
4
5
6
7
8
9
10
11
12$ gawk '{
> total = 0
> for (i = 1; i < 4; i++)
> {
> total += $1
> }
> avg = total / 3
> print "Average:",avg
> }' number
Average: 130
Average: 160
Average: 145
5. 格式化输出
printf 命令格式:printf "format string", var1, var2 . . .
常用格式控制符如下表所示:
控制字符 | 描述 |
---|---|
c | 将数字显示为对应的 ASCII 字符 |
d 或 i | 显示整数 |
e | 将数字以科学记数法显示 |
f | 显示浮点数 |
g | 以科学计算法或浮点数显示(看哪种更短) |
o | 以八进制显示 |
s | 显示字符串 |
x | 以十六进制显示 |
X | 以十六进制显示,使用大写的 A-F |
1 | $ gawk 'BEGIN{ |
除控制字符以外,还可以使用另外三种修饰符以对输出进行更多的控制。
- width :该数值用于指定输出的最小宽度。如长度不够,用空格补充
- prec :该数值用于指定浮点数的精确度,或者字符串能包含字符的最大数量
- -(减号):格式化输出时,使用左对齐
1 | $ gawk 'BEGIN{FS="\n"; RS=""} {printf "%16s %s\n", $1, $4}' people.txt |
参考下面的示例, %10.1f
中的 10
用于指定字段的最小宽度(右对齐,前面用空格补),.1
用于指定精确度。1
2
3
4
5
6
7
8
9
10
11
12$ gawk '{
> total = 0
> for (i = 1; i < 4; i++)
> {
> total += $i
> }
> avg = total / 3
> printf "Average: %10.1f\n",avg
> }' number
Average: 128.3
Average: 137.7
Average: 176.7
6. 内建函数
数学函数
函数 | 描述 |
---|---|
atan2(x,y) | x / y 的正切 |
cos(x) | x 的余弦 |
exp(x) | x 以 e 为底的指数 |
int(x) | x 的整数部分 |
log(x) | x 的自然对数 |
rand() | 生成介于 0 和 1 之间的随机数 |
sin(x) | x 的正弦 |
sqrt(x) | x 的平方根 |
srand(x) | 指定生成随机数的种子 |
1 | $ gawk 'BEGIN{x=exp(100); print x}' |
字符串函数
函数 | 描述 |
---|---|
gensub(r, s, h [, t]) | 该函数用于检索字符串(默认为 $0 ,如 t 指定,则检索字符串 t),用正则表达式 r 进行匹配,并将匹配结果替换为 s 。如 h 为 “g” 或 “G” ,则执行全局替换;如 h 为数字,则只将第 h 个匹配项替换为 s |
gsub(r, s [,t] | 该函数用于检索字符串(默认为 $0 ,如 t 指定,则检索字符串 t),用正则表达式 r 进行匹配,并将匹配结果替换为 s (全局替换) |
index(s, t) | 该函数用于返回字符串 t 在字符串 s 中的位置索引(如 s 不包含 t ,则返回 0) |
length([s]) | 该函数用于返回字符串 s 的长度,如 s 未指定,则返回 $0 的长度 |
match(s, r ) | 该函数用于返回字符串 s 中正则表达式 r 的位置索引 |
split(s, a [,r]) | 该函数用于将字符串 s 根据 FS 符分割后的字段保存在数组 a 中。如已指定正则表达式 r ,则根据 r 而不是 FS 进行分割 |
sprintf(format, variablies) | 该函数用于返回一个格式化后的字符串,该字符串类似 printf 函数的输出 |
sub(r, s [,t]) | 该函数用于检索指定字符串 t (如果未指定 t ,则检索 $0),并使用 s 替换第一个符合条件的匹配结果 |
tolower(s) | 将字符串 s 中的所有字符转换成小写 |
toupper(s) | 将字符串 s 中的所有字符转换成大写 |
1 | $ gawk 'BEGIN{ |
时间函数
函数 | 描述 |
---|---|
mktime(datespec) | 将普通格式( YYYY MM DD HH MM SS )的时间日期转换成时间戳 |
strftime(format [,timestamp]) | 将指定时间戳(如未指定,使用当前时间戳)转换成指定的时间日期格式 |
systime() | 返回当前时间的时间戳 |
1 | $ gawk 'BEGIN{ |
用户自定义函数
定义函数1
2
3
4function name([variables])
{
statements
}
使用函数1
2
3
4
5
6
7
8
9
10
11$ gawk '
> function myprint()
> {
> printf "%-16s - %s\n", $1, $4
> }
> BEGIN{FS="\n"; RS=""}
> {
> myprint()
> }' people.txt
Riley Mullen - (312)555-1234
Frank Williams - (317)555-9876
函数库
创建函数库1
2
3
4
5
6
7
8
9
10
11
12
13$ cat funclib
function myprint()
{
printf "%-16s - %s\n", $1, $4
}
function myrand(limit)
{
return int(limit * rand())
}
function printthird()
{
print $3
}
调用函数库1
2
3
4
5
6
7
8
9$ cat script
BEGIN{ FS="\n"; RS=""}
{
myprint()
}
$
$ gawk -f funclib -f script people.txt
Riley Mullen - (312)555-1234
Frank Williams - (317)555-9876