第十一章 awk的系列产品

原始的awk(V7 最早版本,功能残缺)

七十年代第七版UNIX自带最老awk,大量现代awk特性没有。

转义序列

仅支持:\t \n \\ \"; \b \r \f大多厂商后期才追加。

求幂

没有 ^、^=、**、**= 求幂运算符。

C语言三元条件表达式

不支持 条件 ? 真表达式 : 假表达式,只能写if‑else。

变量直接作为布尔模式不支持

老版本不能写:

1
flag { print ... }

必须写比较表达式:

1
flag != 0 { print "..." }

伪动态正则

原始awk正则必须写死在脚本里,不能直接把awk变量当做正则模式。
变通方案:shell拼接字符串传入awk。

1
2
3
#!/bin/sh
search=$1
awk '$1 ~ /'"$search"'/' acronyms

shell参数默认值写法:

1
2
search=$1
awk '$1 ~ /'"${search:-.*}"'/' acronyms

控制流行为差异

  1. 如果脚本只有BEGIN块,没有其他规则:POSIX‑awk执行完BEGIN直接退出;原始V7‑awk还会继续等待输入。
    • 解决:要么传 /dev/null 作为输入文件,要么在BEGIN末尾写exit。
  2. BEGIN、END只能各出现一次,并且必须分别放在脚本开头、脚本末尾。

字段分隔

字段分割逻辑大体相同,但FS不能使用正则表达式。

数组功能残缺

  1. 没有delete array[下标]删除数组元素;只能赋值为空字符串,代码自己判断跳过空值。
  2. 没有in操作符,不能用 if(key in arr) 判断下标是否存在;只能for(key in arr)循环遍历全部元素比对。
1
2
3
4
5
6
7
# 原始awk判断key是否存在,只能循环遍历
for (item in array){
    if (item == searchkey) {
        # 找到,处理
        break
    }
}

getline

原始V7‑awk完全没有getline;部分厂商补丁只实现最简单读主输入流的getline,不支持读文件、管道,也没有FNR。

内置函数(原始awk)

表11‑1 原始awk内置字符串函数

函数 说明
index(s,t) 返回子串t在s中的位置,找不到返回0
length(s) 返回字符串长度;无参数取$0
split(s,a,sep) 拆分字符串到数组a,返回元素个数
sprintf(fmt,expr) 格式化,返回字符串
substr(s,p,n) 截取子串

表11‑2 原始awk算术函数

函数 说明
exp(x) e的x次幂
int(x) 截断取整
log(x) 自然对数
sqrt(x) 平方根

原始awk:不支持用户自定义function函数。

原始awk内置变量

表11‑3

变量 含义
FILENAME 当前输入文件名
FS 输入字段分隔符,默认空格,一行(记录)内部,切分成 $1 $2
NF 当前记录字段数
OFMT 数字输出转换格式,默认%.6g
OFS 输出字段分隔符,print $1,$2 逗号输出时,字段之间用什么隔开,默认空格
ORS 输出记录分隔符,每一条记录 print 输出完末尾加什么,默认换行\n
RS 把整个大文件切分成一条条记录(行),默认换行\n;RS=""代表空行分隔多条记录

原始awk没有CONVFMT,数字转字符串使用OFMT。

可免费使用的awk实现

三个主流免费版本:

  1. Bell Labs awk(nawk,Brian Kernighan官方原版)
  2. GNU awk(gawk,Linux默认)
  3. mawk,Michael Brennan实现,速度很快

下面是三者都支持的扩展(不属于POSIX标准,可移植脚本慎用)

共有的扩展功能

  1. delete array 不带下标,一次性清空整个数组
1
2
3
4
5
# POSIX标准:只能逐个删元素
for(i in data) delete data[i]

# 扩展写法,一次性清空整个数组
delete data

注意:数组一旦定义为数组,就不能再当做普通标量变量使用。

  1. 字符拆分扩展
  • FS="":每一个字符单独作为一个字段。
  • split(str,arr,""):把字符串每个字符拆进数组。

POSIX标准没有该能力;没有扩展只能反复调用substr()逐个取字符。

  1. fflush() 刷新输出缓冲区

POSIX没有;参数是文件名/管道;强制把缓冲区数据写入文件,不等缓冲区填满。

  1. 通用特殊文件名(写字符串引号包裹)
文件名 描述
/dev/stdin 标准输入;mawk不支持,mawk用"-"
/dev/stdout 标准输出
/dev/stderr 标准错误输出
/dev/tty 用户终端屏幕;所有版本都能用

示例,打印错误直接输出到终端,不受重定向影响:

1
2
3
function printerr(message) {
    printf("ERROR: %s (%d) %s\n", message, NR, $0) > "/dev/tty"
}
  1. nextfile 语句

跳过当前整个文件,直接处理下一个输入文件;next是跳过单条记录,nextfile跳过整个文件。不属于POSIX。

  1. RS支持正则表达式(gawk、mawk)
  • POSIX标准:RS只能单个字符;
  • gawk/mawk:RS可以写正则;gawk额外提供RT变量保存实际匹配到的分隔文本。

Bell Labs awk(nawk)

awk作者官方实现;System V 4、SunOS4.1.x自带。

  • 1987版本:实现大部分POSIX特性,缺少CONVFMT,用OFMT。
  • 1989版本新增:
    1. -v选项(BEGIN前设置变量)
    2. tolower()、toupper()
    3. 多个‑f加载脚本库文件
    4. ENVIRON环境数组
    5. srand()返回旧种子;printf动态宽度精度
  • 1993公开源代码,增加fflush(),增加CONVFMT。
  • 1996版本吸收gawk/mawk很多扩展特性。

GNU awk — gawk(Linux系统默认awk)

功能最强扩展最多;很多扩展非POSIX。

gawk命令行选项

选项 功能
--lint 告警,提示使用非可移植扩展语法
--lint‑old 提示原始V7‑awk不兼容的代码
--traditional 关闭gawk特有扩展,模拟Bell Labs nawk
--posix 严格POSIX模式,禁用所有扩展
--re‑interval 开启正则{m,n}区间量词
--source '脚本' 和‑f配合,内联脚本+外部库文件一起运行

示例:

1
gawk --source '{print $1}' -f mylib.awk data.txt

AWKPATH环境变量

gawk查找‑f指定脚本文件的搜索路径;类似shell的PATH。

如果文件名包含/(绝对/相对路径),不会走AWKPATH搜索。

gawk扩展正则表达式

表11‑5

符号 含义
\w 匹配单词字符:字母、数字、下划线 [A‑Za‑z0‑9_]
\W 匹配非单词字符:不是字母 / 数字 / 下划线的任意字符
\< 单词开头边界:单词字符的前面那个缝隙位置,匹配:前面不是单词字符,后面是单词字符 的位置。
\> 单词结尾边界:单词字符的后面那个缝隙位置,匹配:前面是单词字符,后面不是单词字符 的位置。
\y 单词边界:等价 \< 或者 \>,开头或者结尾都可以
\B 非单词边界,单词字符和单词字符中间的缝隙;或者非单词和非单词中间缝隙
\` 字符串(记录)开头位置,等价 ^
\' 字符串(记录)结尾位置,等价 $

示例

表达式 可以匹配 不能匹配 简要说明
\<away away、(away) stowaway 单词开头,away前面不能是单词字符
stow\> stow、stow. stowaway 单词结尾,stow后面不能是单词字符
\yballs?\y ball、balls、ball, ballroom、baseball 单词的前后任意一边边界
\Brat\B crate dirty rat rat的前后都必须是字母(非单词边界)
\w a、7、_ 空格 , ! 单个单词字符
\W 空格 , # b 3 _ 单个非单词字符
\`test test ok aa test ok 记录开头,同^test
ok\' hi ok hi ok!! 记录结尾,同ok$

gawk特有变量

表11‑8

变量 说明
ARGIND 当前输入文件,在ARGV数组里面的下标索引
ERRNO getline / close出错时,保存错误描述字符串
FIELDWIDTHS 固定宽度字段,按字符长度切分字段;用于没有分隔符的固定宽度文本
IGNORECASE 非0则正则、字符串匹配忽略大小写;不影响数组下标
RT RS为正则时,保存实际匹配到的记录分隔符文本

FIELDWIDTHS示例

1
2
3
BEGIN{ FIELDWIDTHS="5 6 8 3" }
# $1占5字符,$2占6字符,$3占8字符,$4占3字符
# 恢复普通FS分隔:FS = FS

gawk特有函数

表11‑9

函数 说明
gensub(r,s,h,t) 增强替换;返回新字符串,不修改原内容;h为"g"全局,数字代表替换第N次匹配;t默认$0;支持\1 \2捕获组引用
systime() 返回Unix时间戳,1970‑01‑01 UTC以来秒数
strftime(fmt,[ts]) 把时间戳格式化人类可读日期时间

gensub示例:

1
echo "part two" | gawk '{print gensub(/(part) (one|two|three)/,"\\2","g")}'

sub/gsub会直接修改原字符串;gensub返回新字符串,源字符串不变。

时间示例:

1
gawk 'BEGIN{ print strftime("Today is %A, %B %d, %Y") }'

gawk特殊文件名

/dev/stdin /dev/stdout /dev/stderr /dev/fd/n;还有一批已经废弃的/dev/pid等,改用PROCINFO[]数组。

mawk

Michael Brennan开发,完全兼容POSIX;运行速度快,跨平台,支持DOS。

  • 支持通用扩展:delete array清空数组、FS=““字符拆分、nextfile、RS正则。
  • 不支持/dev/stdin,用"-"代表标准输入。
  • 扩展数量少于gawk,追求速度。

商业版本awk

MKS awk(Windows)

运行于Windows;POSIX兼容;新增ord()函数,取字符ASCII码。

tawk(Thompson Automation)

编译器型awk,不是解释器,可以编译生成独立exe;调试器;大量高级扩展。 重要扩展特性:

  1. INIT、BEGINFILE、ENDFILE特殊模式;BEGINFILE文件打开前执行,空文件也触发。
  2. 正则可以加标志:/pattern/i忽略大小写。
  3. 真正多维数组 a[1][1](标准awk是模拟多维a[1,1])。
  4. 数组遍历for(in)可以可控排序。
  5. FPAT变量:按正则模式定义字段内容,而不是分隔符。
  6. RECLEN读取定长二进制记录。
  7. 十六进制字面量0x10;大量系统调用、目录、文件操作内置函数。

VSAwk

Visual Basic组件,把awk的处理模型移植到VB环境。

第十一章核心速记清单

  1. 原始V7‑awk:残缺,无getline、无自定义函数、无in数组判断、无**求幂、不支持三元运算符。写脚本不要假设目标机器是原始awk。
  2. POSIX‑awk标准能力:是可移植脚本的底线;很多好用特性(delete arr、fflush()、nextfile、RS正则)属于扩展,不一定所有环境都有。
  3. 三大免费实现:
    • Bell‑Labs nawk:官方原版。
    • gawk(Linux默认):扩展最多;--posix/--traditional切换模式;gensub()、时间函数strftime()、IGNORECASE、FIELDWIDTHS、RT。
    • mawk:速度优先,扩展少一点。
  4. 重要区分:
    • next:跳过当前一条记录;
    • nextfile:跳过整个输入文件,直接下一个文件(扩展,非POSIX)。
  5. 特殊文件:/dev/tty全部awk通用,输出直接到终端,不受shell重定向影响。
  6. 可移植脚本开发原则:
    • 尽量只用POSIX规定语法;
    • gawk开发完,打开gawk --posix做兼容性检查;
    • 不要依赖gensub、nextfile、FIELDWIDTHS这类扩展,否则换到其他UNIX会报错。
  7. tawk是编译器商业版本,有真正多维数组、FPAT、BEGINFILE等大量高级扩展。

小提示:Linux上awk实际是gawk;FreeBSD/macOS上默认awk接近nawk/mawk,扩展少,做跨平台脚本要格外小心。