awk 详解

awk 是一个强大的文本分析工具,相对于 grep 的查找,sed 的编辑,awk 在其对数据分析并生成报告时,显得尤为强大;简单来说 awk 就是把文件逐行的读入,以空格为默认分隔符将每行切片,切开的部分再进行各种分析处理。
awk 其名称得自于它的创始人 Alfred Aho 、Peter Weinberger 和 Brian Kernighan 姓氏的首个字母;awk 有 3 个不同版本:awk、nawk 和 gawk,未作特别说明,一般指 gawk,gawk 是 awk 的 GNU 版本。
实际上 awk 的确拥有自己的语言:awk 程序设计语言,三位创建者已将它正式定义为“样式扫描和处理语言”;它允许您创建简短的程序,这些程序读取输入文件、为数据排序、处理数据、对输入执行计算以及生成报表,还有无数其他功能。

sed、awk

awk 和 sed 一样,都是以行为处理单位,每次只读取一行,然后将这行字符串从命令序列中过一遍,最后输出,直到文件末尾;
sed 支持基本正则 BREs、扩展正则 EREs;awk 支持扩展正则 EREs;sed 专注于文本处理顺便支持脚本编程;awk 专注于脚本编程顺便支持文本处理。

我们不能说 sed 和 awk 哪个更好,它们都是互补的,各有各的长处,各有各的短处,往往需要结合使用;
我个人认为,如果是单纯的文本编辑、文本替换,则选择 sed;如果需要将文本按列处理,则选择 awk。

sed - 详解 一文中,我们说到,sed 其实很像一门编程语言,甚至还有 goto 语句,那么现在,我们可以肯定的说:awk 就是一门编程语言,它支持变量、数组、函数、循环、条件分支、输出重定向、语句块等,同时还内置了非常多且实用的 built-in 函数。

工作流程

awk 工作流程

如上图所示,awk 遵循简单的工作流程:读取,执行和重复,这和 sed 是一样的;
启动程序之后,awk 首先会执行 BEGIN 语句块;在退出进程之前会执行 END 语句块;这是与 sed 不同的地方;
但是,BEGIN 初始化块和 END 结束语句块都不是必须的,它们可以都没有,也可以都有;
除了 BEGIN 块和 END 块,awk 最主要的就是主循环块了(定义如何处理一条记录);

  • BEGIN { awk-commands },非必须,通常用于初始化变量;
  • { awk-commands },每行记录都会执行 awk-commands;
  • pattern { awk-commands },与 pattern 匹配的行执行 awk-commands;pattern 如下:
    • /regex/,所有与 regex 匹配的行;
    • expression,测试 expression 条件为真的行(数字非零、字符串非空);
  • pattern1, pattern2 { awk-commands },在 pattern1 和 pattern2 中的行执行 awk-commands;
  • END { awk-commands },非必须,通常用于统计某些信息。

同时,在 awk 中有两个概念要搞清楚:

  • 记录(record),每个输入行被称作是一个记录;使用$0特殊变量可引用;
  • 域(field),每个记录会被分隔符(默认为空格和制表符,且多个连续空格或制表符会当作一个进行处理)分割;使用$N(N 为正整数)特殊变量可引用。

命令行选项

从命令行中读取 script:awk [OPTIONS...] 'script' [FILES...]
从脚本文件中读取 script:awk [OPTIONS...] -f 'script-file' [FILES...]
和 sed 一样,如果省略 FILES,则默认从标准输入文件中读取数据并进行处理。

简单例子

内置变量

  • ARGC:参数个数,其值总是大于等于 1;
  • ARGV:参数列表,ARGV[0]是可执行文件名;
  • ARGIND:当前命令行参数索引(GNU);
  • ENVIRON:当前环境变量数组,如,{ print ENVIRON["USER"] }获取当前用户名;
  • SUBSEP:数组下标分隔符,默认为\034
  • CONVFMT:定义数字转换格式,默认值为%.6g
  • OFMT:输出格式化字符串,默认值为%.6g
  • FILENAME:当前操作的文件名,注意,在 BEGIN 块中此变量还未定义;
  • $0:当前输入记录;
  • $N:当前输入记录中的第 N 个字段;
  • RS:输入记录分隔符,默认为换行符;
  • FS:输入字段分隔符,默认为空格/制表;
  • NR:当前记录编号,多个文件时会累加;
  • NF:当前记录中的字段数量;
  • FNR:当前文件记录编号,多个文件时不进行累加;
  • ORS:输出记录分隔符,默认为换行符;
  • OFS:输出字段分隔符,默认为空格;
  • RSTART:match() 函数设置的变量,表示匹配子串的起始索引(从 1 开始);
  • RLENGTH:match() 函数设置的变量,表示匹配子串的长度,如if(match("google","goo")) print RLENGTH输出 3;
  • IGNORECASE:默认为 0,设为 1 后将忽略大小写(如 pattern 匹配时将忽略大小写);
  • PROCINFO:当前 awk 进程信息数组,如 PID、有效 UID 号等,BEGIN { print PROCINFO["pid"] }
  • ERRNO:getline() 函数操作失败时设置的错误,可使用 print 打印错误原因;

运算符

支持的运算符和 C/C++、Java 的非常相似,仅有细微差别:
算术运算符+加、-减、*乘、/除、%取余、**^乘方;
自增自减符++a前自增、--a前自减、a++后自增、a--后自减;
赋值运算符=+=-=*=/=%=**=^=
关系运算符==!=>>=<<=
逻辑运算符&&||!
三元操作符cond ? statement1 : statement2
一元操作符+乘以 +1、-乘以 -1;如BEGIN{ a=10; printf("%d %d\n", +a, -a) }输出 10、-10;
字符串拼接:空格,如print "a = " "100"
数组运算符:即下标,如arr[0]arr["start"],数组实际是一个 Hash 表;
正则运算符~正则匹配、!~正则匹配(取反),如"www.zfl9.com" ~ "w+"为真;

数组

语法:array_name[index] = value
数组不需要提前声明,数组下标可以不连续,并且数组下标可以为字符串;如arr["index"]arr[4]
在 awk 中,声明变量不需要指明数据类型,因为它们都是字符串;而数组其实是一个类似 hash 表的东西;
并且,如果 index 中存在逗号,则会忽略逗号间的空格,如:arr[1,"www"]arr[1, "www"]是一样的;

删除元素:delete array_name[index]

遍历元素,使用 for 循环,有两种方式:

流程控制

条件分支
if (cond) { statement },如果 statement 只有一条语句,可以省略花括号,这和 C/C++、Java 是一样的;
if (cond) { statement } else { statement }
if (cond) { statement } else if (cond) { statement } else if (cond) { statement } else { statement }

循环
for (init; cond; increment/decrement) { statement },如果 statement 只有一条语句,可以省略花括号;
for (index in array) { statement },专用于数组(哈希表)的遍历;
while (cond) { statement },while 循环;
do { statement } while (cond),do…while 循环;

跳出循环
continue,直接开始下轮循环;
break,结束循环体,执行后面的语句;

退出进程
exit(exit_code)函数,其中 exit_code 在区间[0, 255]

内置函数

print(string),打印传入的参数(数字、字符串等),自动在末尾添加换行符;
printf(fmt-str, args...),格式化打印函数,基本同 C 语言的 printf() 函数;
sub(regex, replace [, target]),单次 regex 替换,第三个参数可选,默认为$0
gsub(regex, replace [, target]),全局 regex 替换,第三个参数可选,默认为$0
gensub(regex, replace, how [, target]),通用 regex 替换(可引用子串),how 为"g""G"表示全局,第四个参数可选,默认为$0,不修改原串,而是返回新串;
index(str, sub),查找子串索引(从 1 开始),成功返回索引值,失败返回 0;
length(str),计算字符串长度并返回;
match(str, regex),匹配子串,并返回索引(从 1 开始),成功返回索引值,失败返回 0;
split(str, arr, regex),使用 regex 分割 str 字符串,然后保存至数组 arr,如果省略 regex,则使用 FS 变量值;
substr(str, start, len),提取子串,start 从 1 开始,len 为提取的长度,如果省略则提取至字符串末尾;
tolower(str),返回传入字符串的小写副本;
toupper(str),返回传入字符串的大写副本;
strtonum(str),从字符串中解析数值,支持八进制、十进制、十六进制,使用不同的前缀即可;
getline(var),读取当前文件(使用重定向符可读取其他文件)的一行保存至 var 变量中,如果省略 var 则默认为 $0;
system(command),调用 shell 执行命令,然后返回命令退出状态码;
next,结束当前行的处理,直接开始处理下一行;
nextfile,结束当前文件的处理,直接开始处理下一文件;
return,在自定义函数中用于返回值;

自定义函数

除了使用内置函数之外,awk 还允许我们定义自己的函数(在 3 个语句块的外面),语法如下:

函数名和变量名的命名规则一样,只能以字母或下划线开头,然后可接字母、数字、下划线;参数 和 return 语句是可选的;

输出重定向

awk 输出重定向的语法和 shell 是一样的,如:
print "string" > filename覆盖,即在打开文件时会清空原文件内容;
print "string" >> filename追加,即在打开文件时不清空原文件内容;

管道
print "string" | "command",启动 shell 执行 command,并将输出传递给 command,作为它的输入;

awk 参考

Awk Tutorial - TutorialsPoint
Awk - A Tutorial and Introduction