陈颂光
全栈工程师,能够独立开发从解释器到网站和桌面/移动端应用的各类软件。
关注我的 GitHub

GAWK概览

AWK语言是一种介于sed与perl之间的语言,同样是文本处理的有力工具。与sed比,AWK有丰富的控制结构,因此易用一些但没有那么紧凑。与perl比,AWK的功能和库都较缺乏,只针对表格式文本,适用面较小。

AWK把输入看作由一系列记录组成,通常一行就是一个记录。每个记录又由一些域组成,通常由空白来分隔。例如,考虑以下输入(来自ls -l /的输出):

drwxr-xr-x   2 root root   4096 9月  13 20:56 bin
drwxr-xr-x   3 root root   4096 9月  13 20:56 boot
drwxr-xr-x   2 root root   4096 9月  13 20:55 cdrom
drwxr-xr-x  20 root root   4340 12月 24 12:32 dev
drwxr-xr-x 140 root root  12288 11月 20 12:47 etc
drwxr-xr-x   3 root root   4096 9月  13 20:55 home
lrwxrwxrwx   1 root root     32 9月  13 20:56 initrd.img -> boot/initrd.img-4.4.0-31-generic
drwxr-xr-x  22 root root   4096 9月  13 20:56 lib
drwxr-xr-x   2 root root   4096 7月  20 04:42 lib64
drwx------   2 root root  16384 9月  13 20:54 lost+found
drwxr-xr-x   3 root root   4096 9月  13 16:18 media
drwxr-xr-x   2 root root   4096 7月  20 04:42 mnt
drwxr-xr-x   2 root root   4096 7月  20 04:42 opt
dr-xr-xr-x 200 root root      0 12月 24 12:31 proc
drwx------   9 root root   4096 12月 18 12:25 root
drwxr-xr-x  24 root root    740 12月 24 12:32 run
drwxr-xr-x   2 root root  12288 9月  26 12:34 sbin
drwxr-xr-x   2 root root   4096 6月  30 04:13 snap
drwxr-xr-x   2 root root   4096 7月  20 04:42 srv
dr-xr-xr-x  13 root root      0 12月 24 12:31 sys
drwxrwxrwt   9 root root 118784 12月 24 12:34 tmp
drwxr-xr-x  10 root root   4096 7月  20 04:42 usr
drwxr-xr-x  14 root root   4096 7月  20 04:53 var
lrwxrwxrwx   1 root root     29 9月  13 20:56 vmlinuz -> boot/vmlinuz-4.4.0-31-generic

其中有24个记录,大部分记录有9个域,也有记录两个有11个域。这样的表格式文本并不少见,包括各种数据文件。AWK和sed同样会逐行处理输入,我们只用告诉它对怎么样的记录怎么样处理。例如ls -l / | gawk 'NF>9 {print $9}'让AWK输出有多于9个域的记录的第9个域:

initrd.img
vmlinuz

调用方式

GAWK命令的两种用法如下:

gawk [ POSIX or GNU style options ] -f program-file [ -- ] file ...
gawk [ POSIX or GNU style options ] [ -- ] program-text file ...

其中常用选项包括:

短选项 长选项 用途
-f program-file –file program-file 指定AWK脚本文件,重复的话会连接起来
-F fs –field-separator fs 指定域分隔符
-v var=val –assign var=val 初始化变量
-b –characters-as-bytes 把每个字节视为字符
-c –traditional 在兼容模式运行
-C –copyright 输出版权信息
-d[file] –dump-variables[=file] 把全局变量的有序列表打印到file或awkvars.out
-D[file] –debug[=file] 运行file或标准输入中调试命令
-e program-text –source program-text 指定AWK脚本
-E file –exec file 类似-f但是用于最后一个选项,常用于#!行以防意外传入代码或选项
-g –gen-pot 生成GNU .pot (Portable Object Template)格式文件以便本地化AWK程序
-h –help 输出帮助
-i include-file –include include-file 加载AWK源(在环境变量AWKPATH找)
-l lib –load lib 加载AWK共享库(在环境变量AWKLIBPATH找),初始化例程应名为dl_load()
-L [value] –lint[=value] 警告可疑的程序构造
-M –bignum 使用任意精度算术
-n –non-decimal-data 识别八进制和十六进制值
-N –use-lc-numeric 用本地的小数点
-o[file] –pretty-print[=file] 把程序格式化地写到file或awkprof.out
-O –optimize 进行优化如尾递归和常量展开
-p[prof-file] –profile[=prof-file] 把性能监视数据写到prof-file或awkprof.out
-P –posix 打开兼容模式
-r –re-interval 容许正则表达式中用区间表达式
-S –sandbox 禁用不安全操作
-t –lint-old 对与UNIX awk不兼容性警告
-V –version 输出版本
环境变量 用途
AWKPATH 指定-f、–file、-i、–include选项寻找文件的目录列表
AWKLIBPATH 指定-l、–load寻找文件的目录列表
GAWK_READ_TIMEOUT 从终端、管道或套接字读取输入的超时(毫秒)
GAWK_SOCK_RETRIES 套接字重试次数
GAWK_MSEC_SLEEP 套接字重试间隔(毫秒)
POSIXLY_CORRECT 相当于–posix选项

SIGUSR1信号导致写性能监视数据和函数调用栈再继续,SIGHUP信号导致写性能监视数据和函数调用栈后退出。

如没有通过exit语句指定退出状态,则正常退出返回0、异常退出返回1、致命错误返回2。

AWK语言

AWK是一种面向行的语言,不过可用反斜杠续行(另外,以,{?:&&||doelse结束的行自动把语句延续到下一行),多个语句也可以用分号分隔放到一行。注释由#开始到行末。AWK程序由一系列下列语句组成:

语句 用途
@include "filename" 包含文件,类似于-i选项
@load "filename" 加载共享库,类似于-l选项
模式 { 一系列动作语句 } 在匹配模式时运行一系列动作语句。省略模式导致匹配所有记录,省略动作相当于{ print }
function 名称(参数列表) { 一系列语句 } 函数定义

其中模式有:

模式 动作运行条件
BEGIN 在开始读入输入前
END 在用完所有输入后或exit语句被运行
BEGINFILE 在开始读入来自一个文件的输入前
ENDFILE 在用完来自一个文件的输入后
/regular expression/ 当前记录匹配指定的egrep正则表达式
relational expression 当前记录使关系表达式为真
pattern && pattern 当前记录同时匹配两个模式
pattern || pattern 当前记录匹配两个模式中至少一个
pattern ? pattern : pattern 当前记录匹配首个pattern时匹配次pattern,否则匹配末pattern
(pattern) 当前记录匹配该模式
! pattern 当前记录不匹配该模式
pattern1, pattern2 从匹配pattern1的记录到匹配pattern2的记录(包含)

AWK程序的运行过程

  1. 进行-v选项指定的变量赋值
  2. 编译程序
  3. 运行各BEGIN规则的代码
  4. 读入ARGV数组指定的各文件(没有指定文件则为标准输入),var=val形式的话则视为变量赋值
  5. 对于每个输入文件: 1. 运行各BEGINFILE规则的代码 2. 对于输入的每条记录:对每条模式匹配它的规则,运行f对应动作 3. 运行各ENDFILE规则的代码
  6. 运行各END规则的代码

常量

八进制和十六进制常量可用C风格,如八进制值011相当于十进制的9,十六进制值0x11相当于二进制的17。

字符串常量由被双引号包围的一系列字符组成,其中可用下列转义序列:

转义序列 意义
\\ 反斜杠
\a 铃响
\b 退格
\f 换页
\n 换行
\r 回车
\t 制表符
\v 垂直制表符
\xhex digits 十六进制数表示的字符
\ddd 一、二或三位八进制数表示的字符
\c 字符c

转义序列也可用于正则表达式(用/包围)。

变量

AWK的变量是动态的,在首次使用时创建,其值为浮点数或字符串。AWK也有一维数组,也可模拟多维数组:GAWK支持数组的数组。数组下标用方括号包围,若下标为形如expr, expr …的表达式,各表达式会由SUBSEP的值分隔连起来。AWK的数组为关联数组,即以字符串作下标。

记录由变量RS指定的单个字符或正则表达式分隔(通常为换行),空表示空行。

若FIELDWIDTHS设为数值列表,则每个域有固定宽度。否则,若变量FPAT设为正则表达式,每个域要匹配它。否则,域从记录由FS指定的正则表达式分隔,单字符表示相继的空格、制表符和换行,空表示每个字符分别为域。RS为空时换行也视为域分隔符。覆盖FS或FPAT会取代FIELDWIDTHS的使用。

各域可依次用$1、$2、……引用(没有则为空),$0引用整个记录,其中域用OFS的值分隔,修改这些变量会导致相应变化。变量NF引用当前记录的域数。

变量和域可以为数值、字符串或两者,如何解读取决于上下文:用在数值表达式中则视为数值,用作预期字符串处则视为字符串。通过加0可强制变量为数值,与空字符串连接则可强制变量为字符串。未初始化变量有数值0和字符串值空。

内置变量
ARGC 命令行参数个数(不含GAWK参数和程序)
ARGIND 当前文件在ARGV的下标
ARGV 命令行参数数组,下标从0到ARGC-1
BINMODE 1或”r”、2或”w”、3或”rw”或”wr”分别表示输入、输出、两者应用二进制I/O
CONVFMT 数值转换格式,默认”%.6g”
ENVIRON 环境变量数组
ERRNO 错误信息
FIELDWIDTHS 由空格分隔的域长度列表,取代FS
FILENAME 当前输入文件(没指定则为”-“),在BEGIN中未定义
FNR 当前文件中记录数
FPAT 域内容满足的正则表达式,取代FS
FS 域分隔符,默认空格
FUNCTAB 用户在程序中定义的函数名的数组
IGNORECASE 字符串操作是否大小写敏感,非零表示不,默认0
LINT 真则打印lint警告,特别地”fatal”时警告变成错误
NF 当前记录的域个数
NR 当前记录数
OFMT 输出数值格式(默认”%.6g”)
OFS 输出的域分隔符(默认空格)
ORS 输出的记录分隔符(默认换行)
PREC 浮点数精度,默认53
PROCINFO 一个运行时信息数组
PROCINFO[“egid”] getegid()系统调用结果
PROCINFO[“strftime”] strftime()的默认时间字符串格式
PROCINFO[“euid”] geteuid()系统调用结果
PROCINFO[“FS”] “FS”、”FPAT”、”FIELDWIDTHS”中正在用的
PROCINFO[“identifiers”] AWK程序标识符类型,可为”array”、”builtin”、”extension”、”scalar”、”untyped”、”user”
PROCINFO[“gid”] getgid()系统调用结果
PROCINFO[“pgrpid”] 当前进程组ID
PROCINFO[“pid”] 当前PID
PROCINFO[“ppid”] 父进程PID
PROCINFO[“uid”] getuid()系统调用结果
PROCINFO[“sorted_in”] 遍历数组元素的顺序,如”@ind_str_asc”、”@ind_num_asc”、”@val_type_asc”、”@val_str_asc”、”@val_num_asc”、”@ind_str_desc”、 “@ind_num_desc”、”@val_type_desc”、”@val_str_desc”、”@val_num_desc”、”@unsorted”或者比较函数的名字,比较函数形如function cmp_func(i1, v1, i2, v2),其中i1和i2为下标,v1和v2为值,它返回负、零、正表示结果
PROCINFO[“input”, “READ_TIMEOUT”] 读输入“input”的超时,非正表示没有
PROCINFO[“mpfr_version”] GNU MPFR库版本
PROCINFO[“gmp_version”] GNU MP 库版本
PROCINFO[“prec_max”] GNU MPFR库容许的最高精度
PROCINFO[“prec_min”] GNU MPFR库容许的最低精度
PROCINFO[“api_major”] 扩展API主版本号
PROCINFO[“api_minor”] 扩展API次版本号
PROCINFO[“version”] GAWK版本
ROUNDMODE “N”或”n”表示偶舍入、”U”或”u”表示向下舍入、”D”或”d”表示向下舍入、”Z”或”z”表示向零舍入,默认”N”
RS 记录分隔符,默认换行
RT 记录结束符,默认为满足RS的输入文本
RSTART match()匹配的字符串开始位置,不匹配则0
RLENGTH match()匹配的字符串长度,不匹配则-1
SUBSEP 数组多重下标的分隔符,默认”\034”
SYMTAB 当前全局变量数组
TEXTDOMAIN AWK程序的文本域

表达式

AWK的运算符按优先级递减为:

运算符 用途
( ) 分组
$ 域引用
++ – 自增、自减,前缀或后缀
^ 求幂
+ - ! 正、负、逻辑非
* / % 乘、除、求余
+ - 加、减
space 字符串连接
| |& 管道I/O
< > <= >= != == 比较
~ !~ 正则表达式匹配或不匹配
in 数组成员
&& 逻辑与
|| 逻辑或
?: 条件
= += -= *= /= %= ^= 赋值

比较两个数值用数值比较,比较一个数值和一个可转换为数值的字符串时也用数值比较,否则用字符串比较。

控制语句

if (condition) statement [ else statement ]
while (condition) statement
do statement while (condition)
for (expr1; expr2; expr3) statement
for (var in array) statement
break
continue
delete array[index]
delete array
exit [ expression ]
{ statements }
switch (expression) {
       case value|regex :
       statement
       ...
       [ default: statement ]
}

I/O语句

I/O语句 用途
close(file [, how]) 关闭文件、管道或协程,选项”to”或”from”用于指定关闭管道的方向
getline 把$0设为下一记录,也设置NF、NR、FNR、RT,成功、文件结束、错误时分别返回1、0、-1
getline <file 把$0设为file的下一记录,也设置NF、RT
getline var 把var设为下一记录,也设置NR、FNR、RT
getline var <file 把var设为file的下一记录,也设置RT
command | getline [var] 运行命令并把输出流进var或$0,设置RT
command |& getline [var] 作为协程运行命令并把输出流进var或$0,设置RT
next 不再处理当前记录
nextfile 不再处理当前输入文件
print 打印当前记录并以ORS的值完结
print expr-list 打印由OFS值分隔的表达式,以ORS值完结
printf fmt, expr-list 格式化打印
system(cmd-line) 运行命令并返回其返回值
fflush([file]) 清洗输出文件file或所有打开的输出文件的缓冲区

其中,print和printf的结果可重定向:

语句 重定向
print ... >> file 覆写文件
print ... >> file 追加到文件
print ... | command 写入管道
print ... |& command 发送到协程或套接字
特殊文件名或命令名 重定向
- 标准输入
/dev/stdin 标准输入
/dev/stdout 标准输出
/dev/stderr 标准错误
/dev/fd/n 文件描述符n指定的文件
/inet/tcp/lport/rhost/rport 从本地端口lport到远程主机rhost端口rport的TCP/IP连接(系统默认)
/inet4/tcp/lport/rhost/rport 从本地端口lport到远程主机rhost端口rport的TCP/IP连接(IPv4)
/inet6/tcp/lport/rhost/rport 从本地端口lport到远程主机rhost端口rport的TCP/IP连接(IPv6)
/inet/udp/lport/rhost/rport 从本地端口lport到远程主机rhost端口rport的UDP/IP连接(系统默认)
/inet4/udp/lport/rhost/rport 从本地端口lport到远程主机rhost端口rport的UDP/IP连接(IPv4)
/inet6/udp/lport/rhost/rport 从本地端口lport到远程主机rhost端口rport的UDP/IP连接(IPv6)

其中printf的格式化字符串中可用如下转换描述:

转换描述 意义
%c 单个字符
%d, %i 十进制整数
%e, %E [-]d.dddddde[+-]dd形式的浮点数
%f, %F [-]ddd.dddddd形式的浮点数
%g, %G %e和%f中较短者
%o 无符号八进制整数
%u 无符号十进制整数
%s 字符串
%x, %X 无符号十六进制整数
%% 字符%

%与控制字符间可插入:

字符 意义
count$ 格式化第count个参数
- 左对齐
空格 不标示正号
+ 总标示符号
# %o导致0前缀,%x、%X导致0x或0X前缀,%e、%E、%f、%F导致总有小数点,%g、%G导致保留尾部的0
0 用0而非空格填充数值
用本地的数值格式
width 填充到长度width,count$表示来自参数
.prec %e、%E、%f、%F指定小数点后位数,%g、%G表示最大有效位数,%d、%i、%o、%u、%x、%X表示最小数字数,%s表示最大字符数,prec也可为count$表示来自参数

内置函数

数值函数 结果
atan2(y, x) y/x的反正切
cos(expr) 余切
exp(expr) 指数
int(expr) 截断为整数
log(expr) 对数
rand() 0 ≤ N < 1 的随机数N
sin(expr) 正切
sqrt(expr) 平方根
srand([expr]) 原随机数种子(同时设置随机数种子为expr或日中的时间)
字符串函数 用途
asort(s [, d [, how] ]) 对数组s的值排序并把下标改为从1开始(指定d则把结果放到d),返回数组s的长度,其中排序准则how可选值与PROCINFO[“sorted_in”]同。
asorti(s [, d [, how] ]) 对数组s的键排序并把下标改为从1开始(指定d则把结果放到d),返回数组s的长度,其中排序准则how可选值与PROCINFO[“sorted_in”]同。
gensub(r, s, h [, t]) 返回把字符串t(默认$0)中正则表达式r的出现换成s(h为g或G时替换所有,h为数值则替换某个)得到的字符串。s中\0或&表示匹配文本、\1到\9表示捕获组,\&表示字面的&。
gsub(r, s [, t]) 把字符串t(默认$0)中正则表达式r的每次出现换成s,返回替换次数。s中&表示匹配文本,\&表示字面的&
index(s, t) 返回字符串t在字符串s中首次出现位置,0表示不出现
length([s]) 返回s(默认$0)的长度
match(s, r [, a]) 返回正则表达式r在字符串s中首次出现位置,0表示不出现,同时设置RSTART和RLENGTH。捕获组入到a(如给出a),下标0给出整个匹配,下标n, "start"n, "length"给出各捕获组范围
patsplit(s, a [, r [, seps] ]) 把字符串s拆分到数组a,其中正则表达式r(默认为FPAT的值)表示域模式,给出seps时分隔符放到seps,seps[i]为seps[i]与seps[i+1]间分隔符,返回所得域个数。
split(s, a [, r [, seps] ]) 把字符串s拆分到数组a,其中正则表达式r(默认为FS的值)表示分隔模式,给出seps时分隔符放到seps,seps[i]为seps[i]与seps[i+1]间分隔符,返回所得域个数。
sprintf(fmt, expr-list) 返回printf格式化的字符串
strtonum(str) 把字符串str解析为数字,前缀0表示八进制,前缀0x或0X表示十六进制,否则十进制
sub(r, s [, t]) 把字符串t(默认$0)中正则表达式r的首次出现换成s,返回替换次数。s中&表示匹配文本,\&表示字面的&
substr(s, i [, n]) 返回s从位置i开始的(至多n个字符的)子字符串
tolower(str) 返回str的小写
toupper(str) 返回str的大写
时间函数 用途
mktime(datespec) 把datespec转换为时间戵,无效则返回-1,其中datespec形如 YYYY MM DD HH MM SS[ DST],月从1到12,日从1到31,小时从0到23,分钟从0到59,秒从0到60,DST负表示自动(默认),采用本地时区。
strftime([format [, timestamp[, utc-flag]]]) 格式化timestamp(默认为当前时间),其中格式format如C语言库,给出utc-flag且为非零或非空时用UTC时间,否则用本地时间
systime() 返回当前时间(POSIX系统上为从1970-01-01 00:00:00 UTC开始秒数)
位函数 用途
and(v1, v2 [, …])
compl(val)
lshift(val, count) 左移
or(v1, v2 [, …])
rshift(val, count) 右移
xor(v1, v2 [, …]) 异或
类型函数 返回值
isarray(x) x是否数组
国际化函数 用途
bindtextdomain(directory [, domain]) 指定文本域domain(默认为TEXTDOMAIN的值)寻找.gmo文件的目录,directory为空时返回当前绑定
dcgettext(string [, domain [, category]]) 返回string在文本域domain对类别category的翻译,domain默认为TEXTDOMAIN的值,category默认为”LC_MESSAGES”.
dcngettext(string1, string2, number [, domain [, category]]) 返回string1和string2在文本域domain对类别category的翻译的number复数形式 ,domain默认为TEXTDOMAIN的值,category默认为”LC_MESSAGES”.

编写和运行本地化的AWK程序:

  1. BEGIN { TEXTDOMAIN = "myprog" }指定文本域
  2. 把需翻译的字符串用下划线标记,如_"hello, world"
  3. 必要时用 dcgettext() 和/或 bindtextdomain()
  4. 运行gawk --gen-pot -f myprog.awk > myprog.pot生成.pot文件
  5. 提供翻译、构建和安装 .gmo 文件

用户定义函数

函数可在模式或动作被调用,函数调用的实参用于实例化函数的形参,数组按引用传递,其它变量按值传递。

局部变量在参数列表中声明,由空格开始,例如function f(p, q, a, b){...}中a、b为局部变量,函数可以互调用和递归。

函数的返回值由return expr中的expr给出,不指定返回值时它不确定。

如果把函数名赋给一个变量,则可用变量名调用它,只要加前缀@。

例子

用途 代码
打印passwd文件中用户名并排序 awk 'BEGIN {FS=":"};{print $1 | "sort"}' </etc/passwd
行计数 { nlines++ };END { print nlines }
加行号 { print FNR, $0 }

更大的例子见/usr/share/doc/gawk/examples中的。

总结

AWK在处理表格样子的文本文件时有其方便之处,由于有类似C的构造,与sed比表达能力强一些,但还是太专用了。同时,它的设计有不少缺陷和历史遗留问题,现在已经基本被perl之类取代。

关键词 unix gawk