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是一种面向行的语言,不过可用反斜杠续行(另外,以,
、{
、?
、:
、&&
、||
、do
、else
结束的行自动把语句延续到下一行),多个语句也可以用分号分隔放到一行。注释由#
开始到行末。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程序的运行过程
- 进行-v选项指定的变量赋值
- 编译程序
- 运行各BEGIN规则的代码
- 读入ARGV数组指定的各文件(没有指定文件则为标准输入),var=val形式的话则视为变量赋值
- 对于每个输入文件: 1. 运行各BEGINFILE规则的代码 2. 对于输入的每条记录:对每条模式匹配它的规则,运行f对应动作 3. 运行各ENDFILE规则的代码
- 运行各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程序:
- 在
BEGIN { TEXTDOMAIN = "myprog" }
指定文本域 - 把需翻译的字符串用下划线标记,如
_"hello, world"
- 必要时用 dcgettext() 和/或 bindtextdomain()
- 运行
gawk --gen-pot -f myprog.awk > myprog.pot
生成.pot文件 - 提供翻译、构建和安装 .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之类取代。