AWK是一种处理文本文件的语言,也是一个强大的文本分析工具。AWK最早是在1977年在贝尔实验室被发明的,之所以叫AWK是因为其取了三位创始人 Alfred Aho,Peter Weinberger, 和 Brian Kernighan 的Family Name的首字符。
本文不会详细讲述AWK的具体语法,如果有兴趣可以去看看《The AWK Programming Language》。本文只介绍AWK的基础知识及其常用的用法。
1. AWK的执行流程
1.1 执行流程
如果想要更灵活的运用AWK,首先需要知道AWK的工作流程,如下图:

AWK的工作流程是从输入流(文件、标准输入)中读一行,执行AWK命令,循环执行上述步骤直到文件结束。
1.2 AWK程序结构
AWK的程序结构主要包括三个部分:BEGIN块、BODY块和END块。
1.2.1 BEGIN块
语法:
BEGIN {awk-commands}
BEGIN块是在AWK程序启动的时候执行,且只执行一次,BEGIN关键字是必须大写,经常用于输出头信息或初始化变量。该块是可选的。
shell> awk 'BEGIN{print "hello world";}'
hello world
1.2.2 BODY块
语法:
/pattern/ {awk-commands}
BODY块对每行输入执行一次,/pattern/
是正则表达式,可以用于过滤行,是可选的,对于BODY块,没有关键字。
shell> cat marks.txt
1) Amit Physics 80
2) Rahul Maths 90
3) Shyam Biology 87
4) Kedar English 85
5) Hari History 89
shell> awk '/a/ {print}' marks.txt
2) Rahul Maths 90
3) Shyam Biology 87
4) Kedar English 85
5) Hari History 89
1.2.3 END块
语法:
END {awk-commands}
END块在程序最后执行,且只执行一次,关键END是必须大写,该块经常用作尾信息或结果的输出。该块是可选的。
shell> echo -e "" | awk 'END{print "end of awk";}'
end of awk
1.3 AWK的基本语法
awk [options] -f program-file file ...
awk [options] program-text file ...
通常awk命令用文本表示,例如:
shell> awk '{print}' marks.txt
也可以将awk命令文本放到文件中,用-f参数指定,如:
shell> cat cmd.awk
{print}
shell> awk -f cmd.awk marks.txt
更多options可以通过awk --help
查看,用的比较多的是-v
和-F
,分别表示赋值和区域分隔符,如
shell> awk -v name="Jerry" 'BEGIN{print "hello" name}'
hello Jerry
shell> awk -F ":" '{print $1}' /etc/passwd | head -n 3
root
bin
daemon
2. 变量
AWK提供了丰富的内建变量,除了内建变量化,也允许用户自定义变量。
2.1 内建变量
前面提到的了变量$1就是内建变量,表示分隔符分隔的第一列值,仍然是marks.txt为例:
shell> cat marks.txt
1) Amit Physics 80
2) Rahul Maths 90
3) Shyam Biology 87
4) Kedar English 85
5) Hari History 89
我们要输出第二列和第四列的值,如下:
shell> awk '{print $2, $4;}' marks.txt
Amit 80
Rahul 90
Shyam 87
Kedar 85
Hari 89
即0表示整行,更多常用内建变量如下:
内建变量 | 说明 |
---|---|
FS | 输入域分隔符(Field Seperator),默认是空格,该内建变量同参数-F |
RS | 输入记录分隔符(Record Seperator),默认是换行 |
OFS | 输出域分隔符(Output Field Seperator),默认是空格 |
ORS | 输出记录分隔符(Output Record Seperator),默认是换行 |
NF | 当前记录中域的数目 |
NR | 当前记录所处的数目 |
FNR | 同NR,但是相对于当前文件,当AWK处理多个文件时,该变量特别有用 |
FILENAME | 当前文件名 |
更多内建变量参考AWK - Built-in Variables,再来看一个使用内建变量的例子:
echo -e "key1:val1\nkey2:val2" | awk 'BEGIN{FS=":"; OFS="=>"} {print "line " NR ": " $1, $2}'
line 1: key1=>val1
line 2: key2=>val2
2.2 自定义变量
除了前面提到的使用-v
参数自定义变量外,我们还可以直接在块中自定义变量,变量定义直接用等号赋值,获取变量值直接引用就可以了。看个例子就明白了:
shell> cat sum.txt
124 127 130
112 142 135
175 158 245
如果我们要计算每一列的和以及平均数,利用awk可以很容易做到:
shell> awk '{sum=$1+$2+$3; average=sum/NF; print "Sum:", sum, "\t", "Average:", average}' sum.txt
Sum: 381 Average: 127
Sum: 389 Average: 129.667
Sum: 578 Average: 192.667
3. 操作符
awk支持丰富的操作符,和C语言非常类似,归纳起来如下:
操作符类型 | 操作符 | ||
---|---|---|---|
算术操作符 |
+ ,- ,* ,、 ,% ,^
|
||
自增自减操作符 |
前置++ ,前置-- ,后置++ ,后置--
|
||
赋值操作符 |
= ,+= ,-= ,*= ,/= ,%= ,^= ,**=
|
||
关系运算符 |
== ,!= ,< ,<= ,> ,>=
|
||
逻辑运行符 |
&& ,` |
, !` |
|
三目运算符 | condition expression ? statement1 : statement2 |
||
一元运算符 |
+ ,-
|
||
字符串拼接 | 字符串使用空格进行拼接,如awk 'BEGIN { str1 = "Hello, "; str2 = "World"; str3 = str1 str2; print str3 }' ,输出Hello, World
|
||
正则表达式操作符 | 使用~ 表示匹配,!~ 表示不匹配 |
这里跟C语言有点区别的是字符串拼接和正则表达式操作符,看下面例子:
#字符串拼接
shell> awk 'BEGIN{str1 = "Hello, "; str2 = "World"; str3 = str1 str2; print str3}'
Hello, World
#正则表达式匹配
shell> awk '$0 !~ 9' marks.txt
1) Amit Physics 80
3) Shyam Biology 87
4) Kedar English 85
4. 内建数组
语法:
# 创建数组
array_name[index] = value
# 删除数据中的值
delete array_name[index]
如:
shell> awk 'BEGIN {
fruits["mango"] = "yellow";
fruits["orange"] = "orange";
print fruits["orange"] "\n" fruits["mango"];
}'
orange
yellow
遍历数组也很简单,可以使用for循环遍历:
shell> awk 'BEGIN {arr["key1"]="val1"; arr["key2"]="val2"; for(i in arr) {print i, arr[i]}}'
key1 val1
key2 val2
5. 正则表达式
一说到正则表达式可能就会面临正则表达式语法的问题,其实在日常应用中,很少用到高级的正则表达式语法,我们只需要记住一些常用的正则表达式语法就够了:
正面表达式元字符 | 说明 | |
---|---|---|
. | 匹配任意单个字符 | |
? | 表示? 之前的字符出现0次或1次 |
|
* | 表示* 之前的字符出现0次或多次 |
|
+ | 表示+ 之前的字符出现1次或多次 |
|
^ | 匹配行的开始 | |
$ | 匹配行的结尾 | |
[] | 匹配字符集合,如[0-9] 匹配任意数字 |
|
[^] | 排除字符集合,是[] 的逆操作,如[^0-9]表示匹配非数字字符 |
|
| | 选择,如果`/Call | Ball/`,表示匹配Call或Ball |
() | 分组,如`(Call | Ball)`,表示匹配Call或Ball |
匹配任意单个字符:
shell> echo -e "cat\nbat\nfun\nfin\nfan" | awk '/f.n/'
fun
fin
fan
匹配开头:
shell> echo -e "This\nThat\nThere\nTheir\nthese" | awk '/^The/'
There
Their
匹配结尾:
shell> echo -e "knife\nknow\nfun\nfin\nfan\nnine" | awk '/n$/'
fun
fin
fan
匹配字符集合:
shell> echo -e "Call\nTall\nBall" | awk '/[CT]all/'
Call
Tall
shell> echo -e "Call\nTall\nBall" | awk '/[^CT]all/'
Ball
选择:
shell> echo -e "Call\nTall\nBall\nSmall\nShall" | awk '/Call|Ball/'
Call
Ball
分组:
shell > echo -e "Apple Juice\nApple Pie\nApple Tart\nApple Cake" | awk '/Apple (Juice|Cake)/'
Apple Juice
Apple Cake
6. 控制语句
AWK的控制语句同C语言语法:
# if condition
if (condition) {
action
}
# if-else
if (condition) {
action-1
} else {
action-2
}
如果是单行,可以省略大括号;
shell> awk 'BEGIN {
num = 11;
if (num % 2 == 0) {
printf "%d is even number.\n", num;
} else {
printf "%d is odd number.\n", num;
}
}'
11 is odd number.
7. 循环语句
同C语言语法,支持for循环和while循环:
7.1 for循环
for (initialization; condition; increment/decrement) {
action
}
如果是单行,可以省略大括号,如下:
shell> awk 'BEGIN { for (i = 1; i <= 3; ++i) print i }'
1
2
3
除此之外for还支持对数组的遍历,语法如下:
for (i in array) {
action
}
shell> cat for.awk
BEGIN{
array[0]=0;
array[1]=1;
array[2]=2;
for (i in array) {
print i;
}
}
shell> awk -f for.awk
0
1
2
7.2 While循环
语法:
do {
action
} while (condition)
当只有一行时大括号可以省略,如:
shell> awk 'BEGIN {i = 1; do { print i; ++i } while (i < 4) }'
1
2
3
8. 函数
8.1. 内建函数
AWK支持很多内建函数,这里用的最多的是print和printf,printf跟C语言的printf很类似,当我们要进行一些格式化输出的时候非常有用,如下:
shell> awk 'BEGIN {
param = 1024.0;
result = sqrt(param);
printf "sqrt(%.2f) = %.2f\n", param, result
}'
sqrt(1024.00) = 32.00
更多内建函数见AWK Build-in Functions。
8.2 用户自定义函数
除了AWK提供的内建函数外,还支持用户自定义函数,自定义函数也跟C语言类似,只是AWK是弱类型语言,没有类型而且,函数定义语法如下:
function function_name(argument1, argument2, ...) {
function body
}
看一个加法的例子:
shell> cat func_demo.awk
function addition(num1, num2) {
result = num1 + num2
return result
}
BEGIN {
res = addition(10, 20)
print "10 + 20 = " res
}
shell> awk -f func_demo.awk
10 + 20 = 30
9. 输出重定向
9.1 重定向操作符
最后来看一下awk的一个非常重要的特性,支持重定向、追回、管道操作符,有什么用呢,先来看一下简单的例子,将AWK的命令输出重定向到文件中,如下:
shell> awk 'BEGIN { print "Hello, World !!!" > "/tmp/message.txt" }'
shell> cat /tmp/message.txt
Hello, World !!!
这有什么用呢,我们可以利用这个特性来拆分文件,如果将不同级别的日志拆分到不同文件中,如下:
shell> cat log.txt
Debug 1560761852 debug log
Trace 1560761883 trace log
Warning 1560761909 warning log
Fatal 1560761923 fatal log
shell> awk '{print > $1}' log.txt
shell> ls
Debug Fatal log.txt Trace Warning
9.2 管道
语法:
print items | command
示例:
shell> awk 'BEGIN { print "hello, world !!!" | "tr [a-z] [A-Z]" }'
HELLO, WORLD !!!
9.3 双向通信
AWK可以使用|&
和一个外部进程进行通信,并将外部进程执行的结果再发送回AWK,这就是所谓的双端通信,这有什么用呢?用处非常大,说明AWK可以跟外部的任何进程进行进程间通信,而非常方便,这里举个例子:如果我们要计算文件中每一行内容的md5值,该怎么做呢?
我们知道md5sum
可以计算文件的md5,但如果要计算文件中的每一行内容的md5,我们可以利用awk
把每一行单独提取出来,然后再将每一行的内容通过|&
传输给md5sum
命令,并将md5sum
执行完成的结果再打印出来就OK了,如下:
shell> cat data.txt
first line
second line
third line
shell> md5_line.awk
{
cmd = "md5sum";
print $0 |& cmd;
close(cmd, "to");
cmd |& getline md5;
print md5;
close(cmd);
}
shell> awk -f md5_line.awk data.txt
e1735158246b267bdc0ec11b0b4c1ecc -
991306dc94d90b914581af4ee2dbd8e6 -
5ffee6590a125f1a159b001625ce599a -
-- 完 --
参考
[1] https://www.tutorialspoint.com/awk/index.htm
[2] https://coolshell.cn/articles/9070.html
[3] https://blyx.com/public/docs/programacion/Awk_Language_Programming.pdf
网友评论