☰
AWK 文本处理实战指南:从“模式-动作“模型到完整脚本(基于 learnxinyminutes-docs 葡语版教程)
2026/10/9 5:29:35 网站建设 项目流程
  • 文档
  • 教程

【免费下载链接】learnxinyminutes-docs

Code documentation written as code! How novel and totally my idea!

项目地址:https://gitcode.com/gh_mirrors/le/learnxinyminutes-docs
点击查看免费下载

AWK 是 POSIX 兼容 UNIX 系统中不可或缺的标准文本处理工具,本指南以 learnxinyminutes-docs 仓库中的 葡语版 AWK 教程 为主体,完整讲解其"模式-动作"编程模型、运算符、数组、内置函数与实战脚本编写。读完本文,你将掌握用 AWK 按行读取文件、按分隔符切分字段、编写统计报告类脚本,以及用命令行与 shebang 两种方式运行 AWK 程序的完整技能。

AWK 是什么:为什么在文本处理场景选择它

AWK 是每个 POSIX 兼容 UNIX 系统中的标准工具,可以把它理解为"去掉冗饰的 Perl"(um Perl despojado),非常适合文本处理任务和其他脚本类需求。它拥有 C 风格语法,但没有强制分号、没有手动内存管理、没有静态类型——这让它在文本处理领域游刃有余。你可以从 shell 脚本中调用它,也可以把它当作独立的脚本语言使用。

为什么用 AWK 而不是 Perl?主要有两点:

  • AWK 是 UNIX 的一部分:你总能在系统上找到它,随时可以依赖它;
  • AWK 比 Perl 更易读:对于简单的文本处理脚本,尤其是"逐行读取文件、按分隔符切分字段"这类任务,AWK 几乎总是正确的工具。

AWK 程序以#!/usr/bin/awk -f作为 shebang,可直接执行;也可以写成awk '模式 { 动作 }' 文件名这样的单行命令。仓库中 英文原版教程 awk.md 还特别提醒:虽然 AWK 不强制要求分号,但在编写**单行命令(one-liner)**时分号是必需的,这也是 AWK 最擅长的场景之一。

程序骨架:模式(pattern)与动作(action)

AWK 程序由一组模式和动作组成,动作放在大括号{ }中,这与 lex 的写法如出一辙:

pattern1 { action1 } pattern2 { action2 }

AWK 背后存在一个隐式循环:它会自动读取你传入的每一个文件的每一行记录(record),并按 FS(字段分隔符,Field Separator,默认是空白,多个空格和 Tab 视为一个)自动切分。你既可以在命令行用-F 字符指定 FS,也可以在 BEGIN 模式里给 FS 赋值。此外还有输出字段分隔符 OFS(默认单个空格),以及输出记录分隔符 ORS 等配套变量。

两个特殊的模式定义了程序的边界:

  • BEGIN:在读取任何文件之前执行为真,是放置初始化代码的地方;
  • END:在最后一个文件(或未指定文件时的标准输入)读到 EOF之后执行为真。

BEGIN 块:变量、运算符与控制流

BEGIN 块在程序一开始执行,适合放置一切前置准备代码;如果没有文本文件要处理,就把它当作主入口。以下是原文档完整演示的 BEGIN 块内容。

变量:全局、免声明

变量是全局的,直接赋值或使用即可,无需声明:

BEGIN { count = 0 }

运算符:与 C 及同类语言一致

# 算术运算符 a = count + 1 # 加 b = count - 1 # 减 c = count * 1 # 乘 d = count / 1 # 整除 e = count % 1 # 取模 f = count ^ 1 # 幂运算 # 复合赋值 a += 1 b -= 1 c *= 1 d /= 1 e %= 1 f ^= 1 # 自增 / 自减 a++ # 后置,返回原值 b-- # 后置,返回原值 ++a # 前置,返回增量后的值 --b # 前置,返回减量后的值

注意:语句末尾不需要分号之类的标点。

控制语句:if/else、三元、while、for

# if / else if (count == 0) print "Começando com count em 0" # 等价于 "Starting with count of 0" else print "Como é que é?" # 等价于 "Huh?" # 三元运算符 print (count == 0) ? "Começando com count em 0" : "Como é que é?" # while 循环(多行块必须用花括号) while (a < 10) { # 字符串拼接:用空格分隔的多个字符串串在一起 print "Concatenação de texto é feita" " com uma série" " de" " textos separados por espaço" print a a++ } # for 循环 for (i = 0; i < 10; i++) print "Uma boa opção para um loop de uma linha"

字符串拼接的写法值得注意:用空格分隔的多个字符串字面量会被直接拼接,无需任何运算符。

比较、逻辑与正则匹配

# 比较运算符(标准集) a < b # 小于 a <= b # 小于等于 a != b # 不等于 a == b # 等于 a > b # 大于 a >= b # 大于等于 # 逻辑运算符 a && b # 与(AND) a || b # 或(OR,包含性) # 正则匹配:~ 匹配、!~ 不匹配 if ("foo" ~ "^fo+$") print "Fooey!" if ("boo" !~ "^fo+$") print "Boo!"

~与!~是 AWK 中极其有用的正则运算符:前者判断左侧字符串是否匹配右侧正则表达式,后者判断是否不匹配。正则的完整语法与 POSIX ERE 一致,这是 AWK 文本处理能力的核心基石。

数组:索引、关联、多维与 in 操作符

AWK 的数组全部本质上是关联数组,可以按索引、按字符串键、甚至按复合键访问:

# 索引数组:逐个赋值 arr[0] = "foo" arr[1] = "bar" # 关联数组:字符串作键 assoc["foo"] = "bar" assoc["bar"] = "baz" # 多维数组(存在一些实现限制) multidim[0,0] = "foo" multidim[0,1] = "bar" multidim[1,0] = "baz" multidim[1,1] = "boo"

葡语版教程提到索引数组"只能用逐行赋值的方式初始化";英文原版 awk.md 补充了更便捷的途径——内置函数split()可以直接用分隔符把字符串劈进数组:

n = split("foo:bar:baz", arr, ":") # arr[1]="foo" arr[2]="bar" arr[3]="baz",n=3

数组相关的常用操作:

# 用 in 测试成员是否存在 if ("foo" in assoc) print "Fooey!" # 用 for (key in array) 遍历所有键 for (key in assoc) print assoc[key] # 用 delete 删除元素 delete assoc["foo"]

命令行参数:ARGV 与 ARGC

命令行参数存放在特殊数组ARGV中,参数个数存放在变量ARGC中:

# 遍历所有命令行参数 for (argnum in ARGV) print ARGV[argnum] # ARGC 是传入的参数个数 print ARGC # delete 对防止 AWK 把参数误当作待处理文件非常有用 delete ARGV[1]

delete ARGV[1]是实战中很常见的技巧:AWK 默认把非选项参数当作待处理的文件名,如果你希望某个参数只是普通数据而不是文件,就必须在处理前把它从 ARGV 中删掉。

函数定义与内置函数三大类

AWK 用function关键字定义函数。这里先讲一个必须掌握的语言特性——"局部变量 hack":

AWK 最令人头疼的一点是没有真正的局部变量,一切皆全局。短脚本中这不是问题,甚至很实用;但长脚本中容易互相污染。 解决办法是:函数参数在函数内是局部的,而 AWK 允许你声明比实际需要更多的参数。把局部变量塞进函数声明即可,并按惯例用额外的空格把它们与真正的参数区分开。

function arithmetic_functions(a, b, c, d) { # a、b、c 是真正的参数;d 前面多留了空格,只是局部变量 ... }

算术类内置函数

function arithmetic_functions(a, b, c, d) { # 多数 AWK 实现提供标准三角函数 d = sin(a) d = cos(a) d = atan2(b, a) # b / a 的反正切 # 对数与指数 d = exp(a) d = log(a) # 平方根 d = sqrt(a) # 截断浮点数的整数部分 d = int(5.34) # d => 5 # 随机数 srand() # 可传种子;默认使用当前时间 d = rand() # 返回 0 到 1 之间的随机数 return d }

字符串类内置函数

作为文本处理语言,AWK 的字符串函数大多重度依赖正则表达式:

function string_functions( localvar, arr) { # 查找并替换:sub 只替换第一处,gsub 替换全部 # 两者都返回被替换的次数 localvar = "fooooobar" sub("fo+", "Meet me at the ", localvar) # localvar => "Meet me at the bar" gsub("e", ".", localvar) # localvar => "M..t m. at th. bar" # match:按正则查找,返回匹配起始位置(从 1 计数) # index() 功能类似,但不支持正则 match(localvar, "t") # => 4,因为 't' 是第 4 个字符 # split:按分隔符切分字符串 split("foo-bar-baz", arr, "-") # arr => ["foo", "bar", "baz"] # 其他实用函数 sprintf("%s %d %d %d", "Testing", 1, 2, 3) # => "Testing 1 2 3" substr("foobar", 2, 3) # => "oob" substr("foobar", 4) # => "bar" length("foo") # => 3 tolower("FOO") # => "foo" toupper("foo") # => "FOO" }

I/O 类内置函数

AWK 并没有真正的文件句柄(file handle)概念:当你用到某个字符串作为 I/O 目标时,AWK 会自动打开对应文件,而这个字符串本身就可以当作句柄使用——这一点让 AWK 用起来很有 shell 脚本的感觉:

function io_functions( localvar) { # 你已经见过 print print "Hello world" # 还有 printf,支持格式化 printf("%s %d %d %d\n", "Testing", 1, 2, 3) # 重定向到文件:把字符串当作文件句柄 print "foobar" > "/tmp/foobar.txt" # 关闭文件句柄 close("/tmp/foobar.txt") # 在 shell 中执行命令 system("echo foobar") # 输出 foobar # getline 的三种来源: # 1) 从标准输入读一行 getline localvar # 2) 从管道读一行(字符串要一致才能正确 close) "echo foobar" | getline localvar # localvar => "foobar" close("echo foobar") # 3) 从文件读一行 getline localvar < "/tmp/foobar.txt" close("/tmp/foobar.txt") }

关于重定向与管道,英文原版 awk.md 给出过一个重要实战建议:输出目标字符串必须与打开时完全一致才能正确关闭,因此更稳妥的写法是先用变量保存文件名/命令字符串,再统一使用该变量(如outfile = "/tmp/foobar.txt"后print ... > outfile、close(outfile))。

处理记录:$0、字段变量与 NF

当你向 AWK 传入参数时,它们会被当作待处理的文件名,按顺序全部处理一遍。可以把这理解成一个隐式 for 循环,迭代这些文件中的每一行;而模式-动作就像循环体内的 switch 语句。

# 对每一行匹配正则 /^fo+bar$/ 的行执行动作 /^fo+bar$/ { # 无参数 print 的默认参数是 $0——当前正在处理的整行 print # 每行在处理前都被隐式切分(像 shell 那样), # 字段用美元符号访问:$1、$2 ... print $2, $4 # 打印第 2、4 个字段 # AWK 自动维护许多辅助变量,最重要的是 NF:当前行的字段数 print NF # 打印当前行的字段数 print $NF # 打印当前行的最后一个字段 }

模式本质上都是真值测试

每个模式其实都是一个 true/false 测试。上面的正则模式也是,只是隐藏了测试对象——如果没指定测试文本,AWK 默认用 $0(当前行)。因此下面两种写法完全等价:

$0 ~ /^fo+bar$/ { print "Equivalente ao último padrão" # 与上一个模式等价 }

模式还可以是任意的条件表达式,例如对每一行判断:

a > 0 { # 只要 a 为正,就对每一行执行一次这个动作 }

这正是 AWK 的哲学:逐行读文件、按分隔符切分、对每行做点什么在 UNIX 中太常见了,以至于 AWK 替你包办了这一切——你只需根据对输入的预期写出模式,再写出想对匹配行做什么。

完整实战:按名字统计平均年龄

下面是一个 AWK 最擅长的典型脚本:从标准输入读入一个名字,然后统计数据文件中所有同名者的平均年龄。

假设数据文件内容如下(作为命令行参数传入):

Bob Jones 32 Jane Doe 22 Steve Stevens 83 Bob Smith 29 Bob Barker 72

完整脚本:

BEGIN { # 1) 先向用户提问 print "Para qual nome você quer calcular a média de idade?" # ("你想为哪个名字计算平均年龄?") # 2) 从标准输入读一行(注意:不是命令行里的文件) getline name < "/dev/stdin" } # 3) 匹配每个第一字段等于所输入名字的行 $1 == name { # 在这个块里,AWK 已为我们预载好一批有用变量: # $0 整行内容 # $3 第三个字段,即年龄,这正是我们要的 # NF 字段数,这里应为 3 # NR 到目前为止读到的记录(行)数 # FILENAME 正在处理的文件名 # FS 正在使用的字段分隔符,这里是空格 " " # ... 还有更多,见 man 手册 # 4) 累加总和与命中行数 sum += $3 nlines++ } # 5) END 模式:在所有文件处理完后执行,适合输出最终报告 END { if (nlines) print "A média da idade para " name " é " sum / nlines # (名字 name 对应人群的平均年龄是 sum / nlines) }

运行方式(假设脚本存为avg.awk):

awk -f avg.awk dados.txt # 输入 "Bob" 后回车,输出:A média da idade para Bob é 44.3333

这个例子集中展示了 AWK 实战的完整闭环:BEGIN 读取交互输入 → 模式匹配过滤记录 → 动作累加聚合 → END 输出报告。其中NR(已读记录数)、FILENAME(当前文件名)、FS(字段分隔符)等变量的存在,意味着即使面对多文件输入,你也能在脚本里精确感知自己处理到哪一步。

仓库佐证:文档组织、校验与多语言版本

本指南主体来自仓库中的 葡语版教程 pt-br/awk.md,其结构严格对照 英文原版 awk.md。英文原版还补充了葡语版省略的若干细节:split()初始化数组、-F命令行选项、FS/OFS 默认值说明,以及"写单行命令务必加分号"的告诫——这些在葡语版正文中未展开,但可以相互印证。

learnxinyminutes-docs 采用"代码即文档"的极简风格(见 README.md):每篇教程的正文就是一个带注释、可直接运行的代码块。仓库还提供了 lint/frontmatter.py 用于校验每篇文档头部 YAML frontmatter 的合法性(允许的键为name、where_x_eq_name、category、filename、contributors、translators,其中 contributors/translators 必须是[作者名, URL]形式的列表),并参考 CONTRIBUTING.md 的规范:代码块行宽不超过 80 字符、优先用示例而非说明文字、整体保持简洁可扫读。这也是 AWK 教程能保持"一行注释配一段代码"高密度的原因。

延伸阅读建议

原文档在结尾推荐了若干进阶资料(均在文档中列为外部链接,此处仅列名称,请自行查阅):

  • Awk tutorial(grymoire 的 Unix/Awk 教程):从入门到进阶的经典系列;
  • Awk man page:man awk是查询内建变量、函数与语义的第一权威来源;
  • The GNU Awk User's Guide:GNU/Linux 系统上最常见的 AWK 实现(gawk)的官方用户指南,对实现细节与边界行为有最完整的说明。

如果你已通读本文并动手跑通了"平均年龄"脚本,就足以在日常日志分析、CSV 预处理、配置文件提取等场景中直接上手 AWK——毕竟,它的全部设计目标就是让你少写代码、多做文本处理。

  • 文档
  • 教程

【免费下载链接】learnxinyminutes-docs

Code documentation written as code! How novel and totally my idea!

项目地址:https://gitcode.com/gh_mirrors/le/learnxinyminutes-docs
点击查看免费下载

相关推荐

上一篇:Amplify JS `@aws-amplify/api` 6.x 演进解析:AmplifyContext 多租户/SSR 架构与 API 能力时间线
下一篇:换歌就自动找词?揭秘foo_openlyrics自动搜索背后的playback回调机制

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询