awk:

Resource based:

  1. https://www.tutorialspoint.com/awk/index.htm

AWK是目前最为著名的GNU/Linux文本编辑工具,它十分强大并且使用它简单明了的编程语言,在几行之内就可以解决复杂的文本处理任务。我们在下文中分别介绍AWK的总体背景、环境、工作流,并会解释具体的awk编程语法,变量定义、操作符、数组和循环,以及在awk中应用的函数。

前置知识:UNIX操作系统基础、Shell编程基础。

awk概览:

AWK是一种集成编程语言,具有强大的文本处理功能,其命名来自于其作者:Alfred Aho, Peter Weinberger, and Brian Kernighan。有许多种awk,我们主要关注最重要也是最流行的awk内容即可。

awk工作流:

要成为awk变成专家,我们需要了解其内在运作的原理:Read, Execute and Repeat。ask会首先从BEGIN所在的代码块开始执行awk命令,然后以此从输入流文件中读取一整行,接着在取到的该行执行awk命令,若解析完该行后依旧没有到达文本文件的末尾,那么会接着重复上述读取、执行的过程,直到最后处理完整个文本文件,接着会在最后执行END代码块中所在的代码。以下摘自tutorial:

1
2
3
4
5
6
7
8
Read
AWK reads a line from the input stream (file, pipe, or stdin) and stores it in memory.

Execute
All AWK commands are applied sequentially on the input. By default AWK execute commands on every line. We can restrict this by providing patterns.

Repeat
This process repeats until the file reaches its end.

程序代码结构:

  1. BEGIN block:

BEGIN代码块的语法格式如下所示:

1
BEGIN {awk-commands}

BEGIN代码块在程序开始的时候执行,而且只执行一次,这里通常会进行变量的初始化,BEGIN是一个awk关键字,因此必须用大写字母全拼,且该代码块是可选的,在无初始化任务时可以不加上该代码块。

  1. Body block:

该部分内容作为awk执行的主体而存在,主题代码块的语法如下所示:

1
/pattern/ {awk-commands}

主题部分的代码会对于每一个输入行都执行一边,在默认情况下,awk会对每一行都执行。我们可以通过提供模式来增加限制,注意body block不存在任何关键字。

  1. END block:

END代码块的语法如下所示:

1
END {awk-commands}

END代码块在程序终止的位置,END作为一个awk关键字同样全部大写,对于下面的文本文件marks.txt:

1
2
3
4
5
1)  Amit    Physics  80
2) Rahul Maths 90
3) Shyam Biology 87
4) Kedar English 85
5) Hari History 89

我们可以用如下的awk脚本来展示文件的内容

1
$ awk 'BEGIN{printf "Sr No\tName\tSub\tMarks\n"} {print}' marks.txt

注意,awk的代码都用单引号包裹,其中BEGIN代码块、body代码块都用花括号包裹,且上述脚本没有END代码块,最后的marks.txt代表将其作为awk的文本文件输入。

awk命令行:

基本的awk代码行格式如下所示:

1
$ awk [options] file ...

相应的,对于在awk脚本文件中的指令,可以这样指定来源的文本文件:

1
2
#!/bin/env awk
awk [options] -f file ...

比如一个awk脚本文件command.awk,其内容为:

1
{print}

我们可以这样在命令行中执行:

1
$ awk -f command.awk marks.txt

awk标准选项:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
-v选项:将一个值赋给某个变量,而且该赋值允许在awk程序运行前就执行,如下所示:
[jerry]$ awk -v name=Jerry 'BEGIN{printf "Name = %s\n", name}'

--dump-variables[=file] 选项:该选项可以将一系列排好序的全局变量以及他们的最终值打印到文件中,默认的输出文件时awkvars.out,当然,我们可以自定义这里的输出文件file

--lint[=fatal] 选项:该选项会检查所有的不可移植的或可疑的构造,当一个参数fatal给定了,就会将警告信息当作error。

--profile[=file] 选项:会将文件内容以一种漂亮的方式生成并打印出来,默认的输出文件时awkprof.out,以下是一个实例,注意,打印的是该命令扩展为的脚本本身。
[jerry]$ awk --profile 'BEGIN{printf"---|Header|--\n"} {print}
END{printf"---|Footer|---\n"}' marks.txt > /dev/null
[jerry]$ cat awkprof.out

输出内容为:
# gawk profile, created Sun Oct 26 19:50:48 2014

# BEGIN block(s)

BEGIN {
printf "---|Header|--\n"
}

# Rule(s) {
print $0
}

# END block(s)

END {
printf "---|Footer|---\n"
}

awk处理案例:

首先,awk通常是用来处理表格为代表的列式文件的,即数据一般是按照列来进行排序。如下例:

1
$ awk '{print $3 "\t" $4}' marks.txt

这里的$3和$4就分别对应源文件的第3列和第4列内容,将其依次输出打印。而且比较特殊的,若print没有参数,则一般而言它会将整行直接输出,也即输出$0($0不代表第0列,而代表整行所有内容)

我们在输出的时候,可以利用模式来进行合理的匹配,如:

1
$ awk '/a/ {print $0}' marks.txt

执行上述指令的结果为:

1
2
3
4
2) Rahul    Maths     90
3) Shyam Biology 87
4) Kedar English 85
5) Hari History 89

也即上述例子实际上是检索了模式a,当模式匹配,则会从主题部分执行对应的代码,也即打印整行内容。

下面这个例子是利用END进行了计数,其本质是到匹配有字符a的行,就会自增计数器,并在END处输出有几行包含了字符a。

1
$ awk '/a/{++cnt} END {print "Count = ", cnt}' marks.txt

下面这个例子,将所有行中超过18个字符的行进行了打印输出,是一个比较特殊的例子。

1
$ awk 'length($0) > 18' marks.txt

awk内置变量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
ARGC: 代表通过命令行传入的参数
$ awk 'BEGIN {print "Arguments =", ARGC}' One Two Three Four
Arguments = 5 ->这里的5分别包括了传入的四个字符串参数,以及执行的脚本名。(awk)


ARGV: 命令行传入参数构成的数组
$ awk 'BEGIN {
for (i = 0; i < ARGC - 1; ++i) {
printf "ARGV[%d] = %s\n", i, ARGV[i]
}
}' one two three four

ARGV[0] = awk
ARGV[1] = one
ARGV[2] = two
ARGV[3] = three

CONVFMT: 代表格式转换的控制字符串,默认为%.6g
ENVIRON: 代表所有环境变量的集合,可以通过方括号加名称来访问。
[jerry]$ awk 'BEGIN { print ENVIRON["USER"] }'
jerry
本质实在用env指令来查询对应的环境比哪里


FILENAME: 表示传入文件的名称
NR: 代表当前记录的数目,如下的例子会在当前记录的数目少于3的时候打印行。本质是一个行计数器,在行数目小于3的时候,将当前行选择出来进行打印。

ERRNO: 代表getline/close等调用失败

awk运算操作符:

1
2
太多了,自己查手册即可:
https://www.tutorialspoint.com/awk/awk_operators.htm

基本上和c是一直或类似的。在花括号的命令内需要用分号分割不同的命令,最后一条命令可以不用;结尾

awk的正则表达式匹配:

本质就是在awk的body部分前面的pattern用正则表达式做行过滤。一系列正则表达式的语句标准都可以应用到这个pattern里。

如下面的代码,使用$作为末尾铆钉位。

1
2
3
4
$ echo -e "knife\nknow\nfun\nfin\nfan\nnine" | awk '/n$/'
$ fun
fin
fan

awk控制流:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
控制:
if (condition) {
action-1
action-1
.
.
action-n
}

[jerry]$ awk 'BEGIN {num = 10; if (num % 2 == 0) printf "%d is even number.\n", num }'
$10 is even number.

[jerry]$ awk 'BEGIN {
a = 30;

if (a==10)
print "a = 10";
else if (a == 20)
print "a = 20";
else if (a == 30)
print "a = 30";
}'
$a = 30

awk循环流:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
for (initialization; condition; increment/decrement)
action

[jerry]$ awk 'BEGIN { for (i = 1; i <= 5; ++i) print i }'
1
2
3
4
5

while (condition)
action

[jerry]$ awk 'BEGIN {i = 1; while (i < 6) { print i; ++i } }'

continue:
[jerry]$ awk 'BEGIN {
for (i = 1; i <= 20; ++i) {
if (i % 2 == 0) print i ; else continue
}
}'
2
4
6
8
10
12
14
16
18
20

exit:
# 比较特殊,可以用来终止脚本的执行,它接受一个参数作为awk进程的返回状态值,如果没有参数,exit会返回0;

[jerry]$ awk 'BEGIN {
sum = 0; for (i = 0; i < 20; ++i) {
sum += i; if (sum > 50) exit(10); else print "Sum =", sum
}
}'

Sum = 0
Sum = 1
Sum = 3
Sum = 6
Sum = 10
Sum = 15
Sum = 21
Sum = 28
Sum = 36
Sum = 45
# 此时查看awk进程返回值,会发现其不为0,而是我们设置的10
[jerry]$ echo $?
10