Awk 是一种文本处理语言,用于在命令行中提取列、过滤记录以及重塑结构化数据。它内置于每个 Linux 和 Unix 系统,能够处理从单行列提取到多阶段数据分析的各种任务。
与大多数过程式编程语言不同,awk 是数据驱动的。你定义一组由“模式(pattern)”和“动作(action)”组成的规则,awk 会自动将这些规则应用到每一行输入上。本指南涵盖 awk 语法、模式、动作、内置变量、字符串函数、数组以及实用示例。
什么是 awk?
awk 是一门用于文本处理的编程语言和命令行工具,以它的三位创造者 Aho、Weinberger 和 Kernighan 的姓名首字母命名。它每次读取一条记录,将每条记录拆分成字段,并对这些字段运行“模式-动作”规则。大多数 Linux 发行版都预装了 gawk(GNU awk)、mawk 或 nawk,它们都实现了相同的 POSIX 核心规范。
常见用途包括:从命令输出中提取特定列、跨行对数值求和或计数、过滤匹配某个模式的日志行,以及重新格式化 CSV 或 TSV 这类结构化数据。对于基于固定分隔符的纯列提取,cut 通常就足够了;一旦你需要条件判断、算术运算或控制输出格式,awk 便是更好的选择。
awk 的工作原理
awk 有多个不同的实现版本。本文将使用 GNU 版本的 awk,即 gawk。在许多 Linux 系统上,awk 命令实际指向 gawk,而在另一些系统上它可能指向 mawk 等其他实现。
记录与字段
awk 处理文本数据文件和流。输入数据被划分为“记录(record)”和“字段(field)”。awk 一次处理一条记录,直到到达输入末尾。记录由被称为“记录分隔符”的字符分隔,默认的记录分隔符是换行符,也就是说文本中的每一行就是一条记录。可以使用 RS 变量来设置新的记录分隔符。
记录由多个字段组成,字段之间由“字段分隔符”分隔。默认情况下,字段以空白字符分隔,包括一个或多个制表符、空格和换行符。
每条记录中的字段通过“美元符号($)”加上从 1 开始的字段编号来引用。第一个字段用 $1 表示,第二个用 $2,依此类推。最后一个字段也可以用特殊变量 $NF 引用,而整条记录则用 $0 引用。
下图直观展示了如何引用记录和字段:
tmpfs 788M 1.8M 786M 1% /run/lock
/dev/sda1 234G 191G 31G 87% /
|-------| |--| |--| |--| |-| |--------|
$1 $2 $3 $4 $5 $6 ($NF) --> fields
|-----------------------------------------|
$0 --> record
awk 程序
要使用 awk 处理文本,你需要编写一个程序来告诉该命令要做什么。该程序由一系列规则以及可选的用户自定义函数组成。每条规则包含一个“模式-动作”对,规则之间以换行符或分号(;)分隔。一个典型的 awk 程序如下所示:
pattern { action }
pattern { action }
...
当 awk 处理数据时,如果模式匹配该记录,它就会对该记录执行指定的动作。当规则没有模式时,每一条输入记录都会被匹配;当规则没有动作时,默认动作是打印整条记录。
awk 的动作被包裹在大括号({})中,由若干语句组成。每条语句指定要执行的操作,多条语句之间以换行符或分号(;)分隔。
编写 awk 程序时,井号(#)之后直到行末的所有内容都被视为注释。长行可以使用续行符“反斜杠(\)”拆分成多行。
执行 awk 程序
awk 程序可以通过多种方式运行。如果程序简短简单,可以直接在命令行中传递给 awk 解释器:
awk 'program' input-file...
在命令行中运行程序时,应该用单引号('')将其包裹起来,以免 shell 解释其中的程序内容。
如果程序庞大且复杂,最好将其放入一个文件,并使用 -f 选项将该文件传递给 awk 命令:
awk -f program-file input-file...
在下面的示例中,我们将使用一个名为“teams.txt”的文件,其内容如下所示:
Bucks Milwaukee 60 22 0.732 Raptors Toronto 58 24 0.707 76ers Philadelphia 51 31 0.622 Celtics Boston 49 33 0.598 Pacers Indiana 48 34 0.585
awk 语法
awk 命令的一般语法如下:
awk [OPTIONS] 'pattern { action }' file
最常用的选项包括:
- -F — 设置输入字段分隔符(与在程序内设置 FS 相同)。
- -v var=value — 在程序开始执行前为变量赋值。
- -f program-file — 从文件读取 awk 程序,而不是从命令行。
当未指定文件时,awk 也可以从标准输入(stdin)读取数据,这让它在管道中非常有用:
command | awk '{ print $1 }'
awk 模式
模式决定了关联的动作是否被执行。awk 支持多种类型的模式,包括正则表达式、关系表达式、区间以及特殊模式。
当规则没有模式时,每一条输入记录都会被匹配。下面是一个只包含动作的规则示例:
awk '{ print $3 }' teams.txt
该程序会打印每条记录的第三个字段:
60 58 51 49 48
正则表达式模式
正则表达式(regex)是一种匹配一组字符串的模式。awk 的正则表达式模式用斜杠(//)括起来:
/regex pattern/ { action }
最基本的正则表达式示例是字面字符或字符串匹配。要显示每条包含“0.5”的记录中的第一个字段,请运行以下命令:
awk '/0.5/ { print $1 }' teams.txt
Celtics Pacers
模式可以是任意类型的扩展正则表达式。下面的示例会打印出“以两个或更多数字开头”的记录的第一字段:
awk '/^[0-9][0-9]/ { print $1 }' teams.txt
76ers
关系表达式模式
关系表达式模式通常用于匹配特定字段或变量的内容。
默认情况下,正则表达式模式是针对整条记录进行匹配的。如果要针对某个字段匹配正则,需指定该字段并使用“包含”比较运算符(~)。
例如,要打印每条“第二个字段包含 ia”的记录的第一字段:
awk '$2 ~ /ia/ { print $1 }' teams.txt
76ers Pacers
要匹配“不包含”某个给定模式的字段,使用 !~ 运算符:
awk '$2 !~ /ia/ { print $1 }' teams.txt
Bucks Raptors Celtics
你可以对字符串或数字进行大于、小于或等于等关系比较。下面的命令会打印出所有“第三个字段大于 50”的记录的第一字段:
awk '$3 > 50 { print $1 }' teams.txt
Bucks Raptors 76ers
区间模式
区间模式由两个用逗号分隔的模式组成:
pattern1, pattern2
从“匹配第一个模式的记录”开始,直到“匹配第二个模式的记录”为止,这中间的所有记录都会被匹配。
下面是一个示例,它会打印从“包含 Raptors 的记录”到“包含 Celtics 的记录”之间所有记录的第一字段:
awk '/Raptors/,/Celtics/ { print $1 }' teams.txt
Raptors 76ers Celtics
这两个模式也可以是关系表达式。下面的命令会打印从“第四个字段等于 31”的记录到“第四个字段等于 33”的记录之间的所有记录:
awk '$4 == 31, $4 == 33 { print $0 }' teams.txt
76ers Philadelphia 51 31 0.622 Celtics Boston 49 33 0.598
区间模式不能与其他模式表达式组合使用。
特殊表达式模式
awk 包含以下特殊模式:
- BEGIN — 在处理记录之前执行动作。
- END — 在处理记录之后执行动作。
BEGIN 模式通常用于设置变量,而 END 模式用于对记录中的数据(例如计算结果)进行处理。
下面的示例先打印“Start Processing.”,然后打印每条记录的第三字段,最后打印“End Processing.”:
awk 'BEGIN { print "Start Processing." }; { print $3 }; END { print "End Processing." }' teams.txt
Start Processing. 60 58 51 49 48 End Processing.
如果程序只有 BEGIN 模式,则执行其动作,但不处理输入;如果程序只有 END 模式,则先处理输入,再执行该规则的动作。
GNU 版本的 awk 还包含两个额外的特殊模式 BEGINFILE 和 ENDFILE,它们允许你在处理文件时执行动作。
组合模式
awk 允许你使用逻辑与运算符(&&)和逻辑或运算符(||)来组合两个或多个模式。
下面是一个示例,它使用 && 运算符打印“第三字段大于 50 且第四字段小于 30”的记录的第一字段:
awk '$3 > 50 && $4 < 30 { print $1 }' teams.txt
Bucks Raptors
awk 动作
awk 动作被包裹在大括号({})中,并在模式匹配时执行。一个动作可以包含零条或多条语句。多条语句按出现顺序执行,且必须以换行符或分号(;)分隔。
awk 支持以下几种动作语句:
- 表达式,例如变量赋值、算术运算符、自增和自减运算符。
- 控制语句,用于控制程序流程(if、for、while、switch 等)。
- 输出语句,例如 print 和 printf。
- 复合语句,用于对其他语句进行分组。
- 输入语句,用于控制输入的处理。
- 删除语句,用于移除数组元素。
print 语句
print 语句是 awk 中最常用的语句,用于打印文本、记录、字段和变量。
打印多个项目时,需要用逗号将它们分隔开。示例如下:
awk '{ print $1, $3, $5 }' teams.txt
打印出的各项之间以单个空格分隔:
Bucks 60 0.732 Raptors 58 0.707 76ers 51 0.622 Celtics 49 0.598 Pacers 48 0.585
如果不使用逗号,各项之间将没有空格:
awk '{ print $1 $3 $5 }' teams.txt
打印出的各项会被拼接在一起:
Bucks600.732 Raptors580.707 76ers510.622 Celtics490.598 Pacers480.585
当 print 不带参数使用时,默认为 print $0,即打印当前记录。
要打印自定义文本,必须用双引号将文本括起来:
awk '{ print "The first field:", $1}' teams.txt
The first field: Bucks The first field: Raptors The first field: 76ers The first field: Celtics The first field: Pacers
你也可以打印换行符等特殊字符:
awk 'BEGIN { print "First line\nSecond line\nThird line" }'
First line Second line Third line
printf 语句
printf 语句让你可以更好地控制输出格式。与 print 不同,printf 在每次输出后不会自动添加换行符。下面是一个插入行号的示例:
awk '{ printf "%3d. %s\n", NR, $0 }' teams.txt
1. Bucks Milwaukee 60 22 0.732 2. Raptors Toronto 58 24 0.707 3. 76ers Philadelphia 51 31 0.622 4. Celtics Boston 49 33 0.598 5. Pacers Indiana 48 34 0.585
常用的 printf 格式说明符包括:%s 表示字符串,%d 表示整数,%f 表示浮点数,%x 表示十六进制。
重定向输出
print 和 printf 都可以将结果写入标准输出之外的地方。只需在语句末尾加上 > 和文件名,即可将结果发送到文件。下面的程序会根据第三字段将球队名称拆分到两个文件中:
awk '$3 > 50 { print $1 > "strong.txt" } $3 <= 50 { print $1 > "average.txt" }' teams.txt
终端中没有任何输出,因为每一次 print 都被重定向了。将第一个文件读回来可以看到匹配到的三支球队:
cat strong.txt
Bucks Raptors 76ers
awk 在第一次打开文件时会截断该文件,同一运行中的后续写入都是追加。如果你想保留文件的原有内容,请改用 >>。
你也可以使用 | 将输出通过管道传递给 shell 命令。下面的示例将每个球队名称发送给 sort:
awk '{ print $1 | "sort" } END { close("sort") }' teams.txt
76ers Bucks Celtics Pacers Raptors
END 块中的 close() 调用会关闭管道并等待 sort 完成。如果没有它,awk 会一直保持管道打开直到程序退出,排序后的名称可能会出现在程序打印的其他内容之后。
控制语句
awk 支持标准的控制流语句。if/else 语句让你能够根据条件执行不同的动作:
awk '{ if ($3 > 50) print $1, "- strong"; else print $1, "- average" }' teams.txt
Bucks - strong Raptors - strong 76ers - strong Celtics - average Pacers - average
for 循环适合用来遍历一段数值范围:
awk 'BEGIN { for (i = 1; i <= 5; i++) print "Square of", i, "is", i*i }'
Square of 1 is 1 Square of 2 is 4 Square of 3 is 9 Square of 4 is 16 Square of 5 is 25
while 循环的工作方式类似。下面是用 while 实现的同一示例:
awk 'BEGIN { i = 1; while (i <= 5) { print "Square of", i, "is", i*i; i++ } }'
next 与 exit 语句
next 语句会停止处理当前记录,并直接跳到下一个记录。它是跳过“剩余规则不应触碰”的记录的常用方式。这里我们跳过包含“76ers”的记录:
awk '/76ers/ { next } { print $1 }' teams.txt
Bucks Raptors Celtics Pacers
第二条规则对于被跳过的记录永远不会执行,因此输出中缺少了“76ers”。
exit 语句会完全停止读取输入,并跳转到 END 块(如果程序有的话)。当你已经找到了要找的内容、没有理由继续读取文件其余部分时,可以使用它:
awk 'NR == 3 { print $1; exit }' teams.txt
76ers
awk 打印出第三条记录的第一字段后便退出,不再读取最后两行。
用户自定义函数
当同一个计算出现在多个规则中时,你可以将它移入一个函数。函数定义以 function 关键字开头,后面跟着函数名、参数列表,以及用大括号包裹的函数体:
function name(parameter, parameter) {
statements
return value
}
函数可以定义在程序中的任何位置,包括调用它的规则之后。下面的程序定义了一个 ratio() 函数,并根据第三和第四字段重新计算胜率:
awk '
function ratio(won, lost) {
return won / (won + lost)
}
{ printf "%s %.3f\n", $1, ratio($3, $4) }
' teams.txt
Bucks 0.732 Raptors 0.707 76ers 0.622 Celtics 0.598 Pacers 0.585
这些数值与文件中的第五字段一致,说明该函数工作正确。
awk 没有用于声明局部变量的关键字。除非出现在参数列表中,否则一切都是全局变量。因此惯例是添加一些调用者永远不会传入的额外参数,并将它们当作局部变量使用。awk 在每次调用时会将它们置为空:
awk 'function join(a, b, sep) { sep = "-"; return a sep b } BEGIN { print join("x", "y") }'
x-y
变量在 awk 中无需声明,初始为空,这让它们非常适合在记录流过时累加一个“运行总和”。下面的命令计算所有行中第三字段存储的数值之和:
累加器变量
编写较长的程序时,你应该创建一个单独的程序文件:
awk '{ sum += $3 } END { printf "%d\n", sum }' teams.txt
266
从文件运行程序
通过将文件名传递给 awk 解释器来运行该程序:
BEGIN {
for (i = 1; i <= 5; i++) {
print "Square of", i, "is", i*i
}
}
你也可以使用 shebang 指令并将 awk 解释器设为可执行,从而像普通可执行文件一样运行 awk 程序:
awk -f prg.awk
保存文件并使其可执行:
#!/usr/bin/awk -f
BEGIN {
for (i = 1; i <= 5; i++) {
print "Square of", i, "is", i*i
}
}
现在你就可以通过输入以下命令来运行该程序:
chmod +x prg.awk
awk 有许多内置变量,它们包含有用的信息并允许你控制程序的处理方式。以下是最常用的内置变量:
./prg.awk
内置变量
下面是一个展示如何打印文件名和行数(记录数)的示例:
- NF — 当前记录中的字段数。
- NR — 当前记录的编号(跨所有文件的行号)。
- FNR — 当前文件中的当前记录编号。与 NR 不同,FNR 在每个新文件时重置为 1。
- FILENAME — 当前正在处理的输入文件名。
- FS — 输入字段分隔符(默认:空白字符)。
- RS — 输入记录分隔符(默认:换行符)。
- OFS — 输出字段分隔符(默认:空格)。
- ORS — 输出记录分隔符(默认:换行符)。
- OFMT — 数字的输出格式(默认:"%.6g")。
OFS 变量控制在使用逗号打印时,字段之间放置什么字符。在下面的示例中,我们将其设置为制表符:
awk 'END { print "File", FILENAME, "contains", NR, "lines." }' teams.txt
File teams.txt contains 5 lines.
NR 统计所有输入文件中的记录总数,而 FNR 在每个新文件时重新从 1 开始计数。只有当你传入多个文件时,这种差异才会显现。在下一个示例中,我们将添加第二个名为“teams2.txt”的文件:
awk 'BEGIN { OFS = "\t" } { print $1, $3, $5 }' teams.txt
Bucks 60 0.732 Raptors 58 0.707 76ers 51 0.622 Celtics 49 0.598 Pacers 48 0.585
现在将两种计数器与文件名一起打印:
Heat Miami 44 38 0.537 Nets Brooklyn 42 40 0.512
NR 持续递增到 7,而一旦 awk 打开第二个文件,FNR 就回退到 1。当程序需要区别对待第一个文件与其余文件时,就要用到这一点。
awk '{ print FILENAME, FNR, NR, $1 }' teams.txt teams2.txt
teams.txt 1 1 Bucks teams.txt 2 2 Raptors teams.txt 3 3 76ers teams.txt 4 4 Celtics teams.txt 5 5 Pacers teams2.txt 1 6 Heat teams2.txt 2 7 Nets
awk 中的变量可以在程序的任意位置设置。若要为整个程序定义一个变量,请将其放在 BEGIN 模式中。
字段分隔符的默认值是任意数量的空格或制表符,可以通过设置 FS 变量来更改。
更改字段与记录分隔符
例如,要将字段分隔符设置为 “.”:
对于多字符分隔符,FS 会被解释为正则表达式。当你需要字面分隔符时,要对正则字符进行转义。例如,要在两个字面点号处拆分记录,可以使用 [.][.]:
awk 'BEGIN { FS = "." } { print $1 }' teams.txt
Bucks Milwaukee 60 22 0 Raptors Toronto 58 24 0 76ers Philadelphia 51 31 0 Celtics Boston 49 33 0 Pacers Indiana 48 34 0
在命令行中运行 awk 单行命令时,可以使用 -F 选项来更改字段分隔符:
printf 'alpha..beta..gamma\n' | awk 'BEGIN { FS = "[.][.]" } { print $1 }'
alpha
默认情况下,记录分隔符是换行符,可以使用 RS 变量来更改。
awk -F "." '{ print $1 }' teams.txt
默认情况下,记录分隔符是换行符,可以使用 RS 变量来更改。
下面是一个将记录分隔符更改为“.”的示例:
awk 'BEGIN { RS = "." } { print $1 }' teams.txt
Bucks 732 707 622 598 585
现在每个点号都结束一条记录,因此第一条记录停在“0.732”之前,下一条记录以“732”开头。由于换行符不再是记录分隔符,它被视为字段之间的普通空白,这就是为什么第一个之后的球队名称永远不会作为 $1 出现。
字符串函数
awk 包含一组丰富的内置字符串函数,用于文本处理。
length() 函数
length() 函数返回字符串中的字符个数。不带参数调用时,它返回当前记录的长度:
awk '{ print $1, length($1) }' teams.txt
Bucks 5 Raptors 7 76ers 5 Celtics 7 Pacers 6
substr() 函数
substr(string, start, length) 函数提取子字符串。start 位置从 1 开始计数。如果省略 length,则返回从 start 到字符串末尾的所有内容:
awk '{ print substr($1, 1, 3) }' teams.txt
Buc Rap 76e Cel Pac
split() 函数
split(string, array, separator) 函数将字符串拆分为数组,并返回元素个数。在下面的示例中,我们拆分一个以冒号分隔的字符串:
echo "one:two:three" | awk '{ n = split($0, a, ":"); for (i = 1; i <= n; i++) print a[i] }'
one two three
sub() 与 gsub() 函数
sub(regex, replacement, target) 函数将目标字符串中正则的第一个匹配项替换为 replacement;gsub() 函数则替换所有匹配项:
echo "hello world hello" | awk '{ gsub(/hello/, "hi"); print }'
hi world hi
如果省略 target,则使用 $0(整条记录)。对于整个文件的简单查找替换,sed 通常更简短。当替换依赖于某个字段、条件或计算时,再使用 gsub()。
tolower() 与 toupper() 函数
这些函数将字符串转换为小写或大写:
awk '{ print toupper($1) }' teams.txt
BUCKS RAPTORS 76ERS CELTICS PACERS
index() 与 match() 函数
index(string, target) 函数返回 target 在 string 中首次出现的位置,找不到则返回 0;match(string, regex) 函数的作用相同,但使用正则表达式:
awk '{ print $1, index($1, "er") }' teams.txt
Bucks 0 Raptors 0 76ers 3 Celtics 0 Pacers 4
数组
awk 支持关联数组(associative arrays),即以字符串作为索引(键)的数组。数组在使用前无需声明。
创建与访问数组
你可以使用 array[key] = value 为数组元素赋值:
awk 'BEGIN { fruits["apple"] = 5; fruits["banana"] = 3; print fruits["apple"], fruits["banana"] }'
5 3
遍历数组
使用 for (key in array) 语法来遍历数组中的所有元素:
awk '{ teams[$1] = $3 } END { for (name in teams) print name, teams[name] }' teams.txt
这将每个球队名称及其胜场数存储起来,最后全部打印。注意,awk 关联数组中元素的顺序是不保证的。
删除数组元素
使用 delete 语句从数组中移除一个元素:
awk 'BEGIN { a["x"] = 1; a["y"] = 2; delete a["x"]; for (k in a) print k, a[k] }'
y 2
统计出现次数
关联数组常用于计数。技巧始终相同:将要计数的值作为数组键,然后对该元素执行自增。teams 文件中没有重复值,因此下面的示例使用了一个名为“events.txt”的小型日志文件:
error disk full warning low memory error disk full info backup done error network timeout
要统计每个严重级别出现的次数,使用第一个字段作为键:
awk '{ counts[$1]++ } END { for (level in counts) print counts[level], level }' events.txt | sort -rn
3 error 1 warning 1 info
awk 为第一个字段中的每个不同值创建一个数组元素,并在每次匹配时自增。END 块在读完整个文件后打印总计。由于 awk 不保证数组元素的顺序,结果通过 sort 管道排序,将出现最频繁的级别放在最前面。
遍历每条记录的字段,可以统计每一个单词,而不只是第一个:
awk '{ for (i = 1; i <= NF; i++) words[$i]++ } END { for (w in words) print words[w], w }' events.txt | sort -rn
3 error 2 full 2 disk 1 warning 1 timeout 1 network 1 memory 1 low 1 info 1 done 1 backup
NF 变量保存当前记录中的字段数,因此无论行有多长,循环都会访问该行上的每个单词。
实用示例
本节展示 awk 的常见实际用途。
处理 /etc/passwd 文件
/etc/passwd 文件使用冒号作为字段分隔符。要列出所有用户名及其登录 shell:
awk -F: '{ print $1, $7 }' /etc/passwd
要查找以 /bin/bash 作为登录 shell 的用户:
awk -F: '$7 == "/bin/bash" { print $1 }' /etc/passwd
这是 Linux 系统上“列出用户”的常用方式的更精确版本,因为它根据登录 shell 进行过滤,而不是打印每个账户。
解析日志文件
要从 Apache 或 Nginx 访问日志(其中 IP 是第一字段)中提取 IP 地址:
awk '{ print $1 }' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -10
该管道提取 IP,用 uniq 统计出现次数,按频率排序,并显示前 10 名。
管道命令
awk 经常用于管道中,从命令输出提取特定列。要显示运行中进程的名称和 CPU 使用率:
ps aux | awk '{ print $11, $3 }' | head -10
要仅显示挂载点和已用百分比的文件系统使用情况:
df -h | awk 'NR > 1 { print $6, $5 }'
NR > 1 模式会跳过表头行。
对列求和
要计算 ls -l 列出的所有文件的总大小:
ls -l | awk 'NR > 1 { sum += $5 } END { print "Total bytes:", sum }'
统计记录数
要统计匹配某个模式的行数:
awk '/error/ { count++ } END { print count }' /var/log/syslog
删除重复行
要在保留原有顺序的同时删除重复行:
awk '!seen[$0]++' file.txt
它的原理是将当前行作为数组键。第一次看到某行时,seen[$0] 为 0(假),因此 !seen[$0] 为真,该行被打印;在后续出现时,该值非零,于是该行被跳过。
CSV 文件处理
要处理不含引号字段的简单 CSV 文件,请将字段分隔符设置为逗号:
awk -F, '{ print $1, $3 }' data.csv
gawk 5.3 及更高版本内置了 CSV 解析器,可处理带引号的逗号、双写引号和内嵌换行符。使用 --csv 选项启用:
gawk --csv '{ print $1, $3 }' data.csv
对于 gawk 4.0 到 5.2 版本,FPAT 变量可以解析停留在单行上的带引号字段:
gawk 'BEGIN { FPAT = "([^,]*)|(\"([^\"]|\"\")+\")" } { print $1, $3 }' data.csv
FPAT 方法会保留外层的引号,且不支持内嵌换行。当你需要完整的 CSV 解析时,请使用 --csv 或专用的 CSV 工具。
在 awk 程序中使用 Shell 变量
如果你在 shell 脚本中使用 awk 命令,经常需要将 shell 变量传递给 awk 程序。一种做法是改用双引号而非单引号包裹程序,并在程序中替换变量。但这种方法会使 awk 程序变得更复杂,因为你必须转义 awk 变量。
在 awk 程序中使用 shell 变量的推荐方式,是通过 -v 选项将 shell 变量赋值给一个 awk 变量。示例如下:
num=51
awk -v n="$num" 'BEGIN {print n}'
51
你也可以传递多个变量:
min=50
max=60
awk -v low="$min" -v high="$max" '$3 >= low && $3 <= high { print $1, $3 }' teams.txt
Bucks 60 Raptors 58 76ers 51
快速参考
如需可打印的快速参考,请查看 Awk 速查表。
| Task | Command |
|---|---|
| Print a specific field | awk '{ print $2 }' file |
| Print multiple fields | awk '{ print $1, $3 }' file |
| Filter by pattern | awk '/pattern/ { print }' file |
| Filter by field value | awk '$3 > 50 { print }' file |
| Set field separator | awk -F: '{ print $1 }' file |
| Use BEGIN/END | awk 'BEGIN { print "Header" } { print } END { print "Footer" }' file |
| Sum a column | awk '{ sum += $3 } END { print sum }' file |
| Count matching lines | awk '/pattern/ { c++ } END { print c }' file |
| Remove duplicates | awk '!seen[$0]++' file |
| Print line numbers | awk '{ print NR, $0 }' file |
| Replace text | awk '{ gsub(/old/, "new"); print }' file |
| Pass shell variable | awk -v x="$var" '{ print x, $1 }' file |
| Run program from file | awk -f script.awk file |
| Print last field | awk '{ print $NF }' file |
| Skip a record | awk '/skip/ { next } { print }' file |
| Stop after a match | awk '/found/ { print; exit }' file |
| Write to a file | awk '{ print $1 > "out.txt" }' file |
| Count per key | awk '{ c[$1]++ } END { for (k in c) print c[k], k }' file |
| Define a function | awk 'function f(x) { return x * 2 } { print f($1) }' file |
常见问题(FAQ)
awk 和 sed 有什么区别?awk 是一门专为基于字段的文本处理而设计的完整编程语言,擅长处理结构化的列式数据;sed 是面向逐行文本转换(如查找替换)的流编辑器。当你需要从特定列提取或计算数值时用 awk,进行简单文本替换时用 sed。
如何在 awk 中打印特定列?使用美元符号加上列号。例如,awk '{ print $2 }' file 会打印第二列。要打印多列,用逗号分隔:awk '{ print $1, $3 }' file。
awk 和 gawk 有什么区别?gawk(GNU awk)是 awk 语言的 GNU 实现,完全兼容 POSIX awk 规范,并增加了 BEGINFILE/ENDFILE 模式、用于基于字段解析的 FPAT 变量以及网络 I/O 等扩展。根据发行版的不同,awk 命令可能指向 gawk,也可能指向其他实现。
如何在 awk 中定义函数?使用 function 关键字,后面跟函数名、参数列表和大括号包裹的函数体,例如 function double(n) { return n * 2 }。定义可以出现在程序中的任何位置,包括调用它的规则之后。awk 没有局部变量关键字,因此需声明调用者永远不会传入的额外参数,并将其当作局部变量使用。
如何在 awk 中使用 CSV 文件?对于不含引号字段的简单 CSV,用 -F, 将字段分隔符设为逗号;在 gawk 5.3 或更高版本中,使用 gawk --csv 处理带引号的逗号、双写引号和内嵌换行;较旧的 gawk 版本可用 FPAT 解析停留在单行上的带引号字段;对于更复杂的 CSV 处理,可考虑使用专用工具(如 Miller)。
总结
awk 是从 Linux 命令行进行列提取、过滤、报表和数据分析的实用工具。如需更深入的语言细节和 GNU 专属特性,请参阅官方 Gawk 手册。