附录二 awk的快速参考

命令行语法

awk两种调用形式

1
2
3
4
5
# 形式1:命令行直接写脚本
awk [-v var=value] [-Fre] [--] 'pattern{action}' var=value datafile(s)

# 形式2:从脚本文件读取awk代码
awk [-v var=value] [-F re] -f scriptfile [--] var=value datafile(s)

选项说明

选项 说明
-v var=value 在BEGIN执行之前就设置变量,BEGIN块内可以使用
-F re 设置字段分隔符FS为正则re
-- 标记选项结束;用于文件名以-开头,防止被识别为命令行选项

注意:命令行直接写 var=value 这种赋值,要等到读取第一条输入记录之后才生效,BEGIN块中访问不到。想要BEGIN可用,必须用‑v。
不提供输入文件,或者文件名写‑,代表从标准输入读取。

用shell实现调用awk

  1. 单行简单脚本直接终端敲;复用脚本放到shell文件,chmod +x赋予执行权限。
  2. #!自执行awk脚本头(现代UNIX/Linux)
1
2
#!/usr/bin/awk -f
#此处写awk脚本内容

注意路径/usr/bin/awk视系统不同可能变化;‑f不能省略。
脚本执行时,命令行传入的参数、文件名直接交给awk处理。

awk语言概要

记录和字段

  • 记录(record):默认一行就是一条记录;由RS记录分隔符控制。
  • 字段(field):一条记录被FS切分成多个字段。
    • $0:整条原始记录
    • $1、$2…:第1、第2个字段
    • $NF:最后一个字段

示例,打印每行第一个字段:

1
{ print $1 }

修改字段分隔符两种写法:

1
2
3
4
5
#方式1:命令行-F
awk -F: -f awkscr /etc/passwd

#方式2:命令行传FS赋值(BEGIN不能读取)
awk -f awkscr FS=: /etc/passwd

设置空行作为记录分隔符(RS="",段落为一条记录),每行作为一个字段:

1
BEGIN { FS="\n"; RS="" }

注意:RS=""(空字符串,按空行分割段落)时,换行符永远作为字段分隔。

脚本的格式

awk脚本由多条规则组成:

1
pattern { action }
  1. 没有pattern:action对所有行执行。
1
{ print } #打印全部输入行
  1. 只有pattern,没有{action}:默认动作等价 print,匹配就打印该行。
  2. 支持自定义函数
1
2
3
function funcname(p1,p2,  localvar) {
    #函数体;参数列表靠后的变量是局部变量
}

调用函数时,函数名和左括号之间不能有空格;定义函数的时候可以有空格。

行的终止

  1. 语句分隔:换行 或者 ;分号;分号可以把多条语句写同一行,降低可读性。
  2. if、for、while后面多条语句必须用大括号包裹,单条语句可以不写大括号。
1
2
3
4
5

if (NF>1) {
    name = $1
    total += $2
}
  1. 代码换行续行:反斜杠\放在行尾;也可以在, { && ||后面直接换行;gawk额外支持? :续行。

标准awk字符串不能跨行;gawk中字符串末尾加\可以换行。

注释

# 开始,到本行行尾结束;不能用反斜杠延续到下一行。

模式

模式可以是下面任意一种:

  1. /正则表达式/
  2. 关系表达式 $1 > 100
  3. BEGIN、END
  4. pattern1 , pattern2 地址范围(从匹配1到匹配2,中间全部记录)
  • ! 放在模式前面:取反,匹配不满足条件的记录。
  • 组合:&&逻辑与、||逻辑或;可以用括号改变优先级。
  • BEGIN:读取第一条输入之前执行;END:全部输入处理完毕之后执行。

多个BEGIN、END会被awk自动合并成一个执行。

正则表达式 表B‑1

优先级从高到低

特殊字符 作用
普通字符c 匹配字面c
\ 转义元字符
^ 字符串开头
$ 字符串末尾
. 任意单个字符(包含换行)
[...] 字符集;^放开头取反;‑表示范围;]放第一位代表字面右方括号
r1|r2 匹配r1或者r2
(r1)(r2) 正则拼接,先后匹配
r* 前面r出现0次或多次
r+ 前面r出现1次或多次
r? 前面r出现0次或1次
(r) 分组

^与$针对整个字符串,不会匹配字符串内部嵌入的换行。

POSIX字符类 表B‑2

写在[[:...:]]内部,可移植,推荐处理多国语言。

符号 含义
[:alnum:] 字母数字
[:alpha:] 字母
[:blank:] 空格、制表符
[:cntrl:] 控制字符
[:digit:] 数字0‑9
[:graph:] 可见非空白字符
[:lower:] 小写字母
[:print:] 全部可打印字符
[:punct:] 标点符号
[:space:] 全部空白
[:upper:] 大写字母
[:xdigit:] 十六进制数字

[.symbol.]排序符号、[=equiv=]等价字符类,实际很少使用。

表达式

常量分为字符串常量(双引号包裹)、数字常量(不加引号)。

转义序列 表B‑3

序列 说明
\a 蜂鸣报警BEL
\b 退格
\f 换页
\n 换行
\r 回车
\t 水平制表
\v 垂直制表
\ddd 八进制字符 1‑3位数字
\xhh 十六进制(gawk扩展,非POSIX标准)

变量

三类变量:自定义变量、内置系统变量(大写)、字段变量$n。

  1. 变量命名:字母数字下划线,不能数字开头;大小写敏感。
  2. 不需要提前声明;未初始化变量:字符串值是空串"",数值是0。awk根据上下文自动决定当作字符串或者数字。
  3. 赋值:var = expr
  4. 字段:$0完整记录;$1…$NF;$NF取最后一个字段。

数组

awk全部是关联数组(哈希):下标可以是数字,也可以是字符串;不保证存储顺序。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15

# 赋值
arr["key"] = 100
arr[123] = "hello"

# 遍历数组,item拿到下标
for(item in arr) {
    print item, arr[item]
}

# 判断下标是否存在(⚠不要用if(arr[key]))
if ("key" in arr) { ... }

# 删除单个元素
delete arr["key"]

gawk/mawk扩展支持 delete arr 直接清空整个数组,不属于POSIX标准。

系统变量 表B‑4

变量 说明 默认值
ARGC 命令行参数总个数
ARGV 命令行参数数组,ARGV[0]是程序名
CONVFMT 数字转字符串格式 %.6g POSIX
ENVIRON 环境变量关联数组,ENVIRON["HOME"]取环境变量
FILENAME 当前正在处理的输入文件名
FNR 当前文件内部记录号,换文件重置为1
FS 输入字段分隔符 空格
NF 当前记录字段数量
NR 全局记录号,跨文件持续累加
OFMT 数字print输出格式 %.6g
OFS 输出字段分隔符,print逗号输出时字段之间的分隔 空格
ORS 输出记录分隔符,每条print末尾追加 \n
RSTART match()匹配内容起始下标
RLENGTH match()匹配到的字符串长度
RS 输入记录分隔符;空串代表按空行分段落 \n
SUBSEP 多维模拟数组下标连接字符,arr[a,b]内部使用 \034

操作符(按优先级从高到低)表B‑5

操作符 功能
$ 字段引用 $1 $NF
++ -- 自增、自减(前缀后缀)
! + - 逻辑非、正号、负号
* / % 乘、除、取模
+ - 加、减
(字符串拼接) 没有符号,直接写变量挨着
< <= > >= != == 比较
~ !~ 正则匹配、不匹配
&& 逻辑与
|| 逻辑或
?: 三元条件表达式
= += -= *= /= %= 赋值

**求幂运算符属于扩展,不是POSIX标准awk。

语句和内置函数汇总

区分:函数有返回值;getline是函数但是不加括号;print/printf属于语句。

名称 简要说明
atan2(y,x) 返回y/x的反正切,弧度
break 跳出for/while/do‑while循环
close(expr) 关闭已经打开的文件或者管道;字符串必须和打开时完全一致,包含空格
continue 直接进入循环下一轮迭代
cos(x) 余弦,参数弧度
delete arr[idx] 删除数组单个元素
do{...}while(expr) 先执行循环体,再判断条件
exit [n] 退出脚本;END块仍然会执行;n设置返回码
exp(x) e的x次方
for(init;cond;inc) stmt C风格for循环
for(idx in arr) stmt 遍历关联数组下标
getline 读取一行;返回1成功,0 EOF,‑1错误。 getline;getline var;getline < file;`cmd
gsub(r,s,[t]) 全局替换;返回替换次数;t省略默认$0
if(cond) stmt [else stmt] 条件判断
index(s,t) 返回子串t在s中的位置,从1开始,找不到返回0
int(x) 截断取整数,丢弃小数部分
length([s]) 返回字符串长度;无参数取$0
log(x) 自然对数
match(s,r) 在s匹配正则r;设置RSTART、RLENGTH;匹配不到返回0
next 读取下一条记录,回到脚本开头重新执行规则
print exprlist [ > file >> file cmd ] 输出;>覆盖;>>追加;输出到管道命令
printf(fmt,args) [重定向] 格式化输出,不会自动加换行
rand() 返回 [0,1) 的随机浮点数;每次运行默认序列相同
sin(x) 正弦,弧度
split(str,arr,sep) 将字符串按照sep切分存入数组arr;sep省略使用FS;返回元素个数
sprintf(fmt,args) 返回格式化后的字符串,不打印输出
sqrt(x) 平方根
srand([seed]) 设置随机数种子;不带参数使用当前时间;返回旧种子
sub(r,s,[t]) 单次替换,只替换第一个匹配;返回1成功,0失败;t省略取$0
substr(s,begin,[len]) 截取子串;起始位置从1开始;len省略取后面全部字符
system(cmd) 执行shell命令;返回退出状态;拿不到命令输出,要用cmd|getline
tolower(s) 全部转小写,返回新字符串
toupper(s) 全部转大写,返回新字符串
while(cond) stmt while循环

printf / sprintf 格式说明 表B‑6

格式模板:%[‑][宽度][.精度]格式符

  • -:左对齐;不写默认右对齐。
  • 宽度:最小输出字符宽度;不足补空格。
  • .精度:浮点数小数点位数;字符串最多输出多少字符。
  • 可以用*动态传入宽度和精度。
1
printf("%*.*g\n",5,3, 3.14159)
格式符 说明
c 输出单个ASCII字符
d / i 十进制整数
e E 科学计数法
f 浮点数
g G 自动选择e/f,去除末尾多余0
o 八进制
s 字符串
x X 十六进制
%% 输出字面百分号%

注意:C库的sprintf是无偏差舍入(银行家舍入),不是四舍五入。附录提供了round()自定义函数实现传统四舍五入。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 传统四舍五入自定义函数

function round(x, ival, aval, fraction)
{
    ival = int(x)
    if (ival == x)
        return x
    if (x < 0) {
        aval = - x
        ival = int(aval)
        fraction = aval - ival
        if (fraction >= .5)
            return int(x) - 1
        else
            return int(x)
    } else {
        fraction = x - ival
        if (fraction >= .5)
            return ival + 1
        else
            return ival
    }
}

速记重点小结

  1. -v var=val:唯一可以在BEGIN块之前设置变量;直接命令行var=valBEGIN不可见。
  2. $0整条记录;$1~$NF字段;$NF最后字段。
  3. 数组全部是关联数组;判断下标存在用 if(key in arr),不能直接if(arr[key])。
  4. NR全局记录号;FNR每个文件单独行号;RS记录分隔;FS字段分隔;OFS/ORS输出分隔。
  5. next:读下一行回到脚本开头;exit退出但END仍然执行。
  6. sub()只替换第一个匹配;gsub()全局全部替换。
  7. system()只能拿到命令退出码,拿不到输出;拿输出必须用管道 cmd | getline。
  8. sprintf()返回字符串,不输出;printf直接输出。
  9. **、delete arr清空整个数组属于扩展,非POSIX,跨平台脚本慎用。
  10. printf *可以动态设置宽度精度;C库默认银行家舍入,需要传统四舍五入自己写round函数。