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之类取代。