ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

AWK 文本处理实战指南:从“模式-动作“模型到完整脚本(基于 learnxinyminutes-docs 葡语版教程)

AWK 文本处理实战指南:从“模式-动作“模型到完整脚本(基于 learnxinyminutes-docs 葡语版教程) 文档教程【免费下载链接】learnxinyminutes-docsCode 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 系统中的标准工具可以把它理解为去掉冗饰的 Perlum 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与动作actionAWK 程序由一组模式和动作组成动作放在大括号{ }中这与 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]bazn3数组相关的常用操作# 用 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关键字定义函数。这里先讲一个必须掌握的语言特性——局部变量 hackAWK 最令人头疼的一点是没有真正的局部变量一切皆全局。短脚本中这不是问题甚至很实用但长脚本中容易互相污染。 解决办法是函数参数在函数内是局部的而 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 语句。# 对每一行匹配正则 /^fobar$/ 的行执行动作 /^fobar$/ { # 无参数 print 的默认参数是 $0——当前正在处理的整行 print # 每行在处理前都被隐式切分像 shell 那样 # 字段用美元符号访问$1、$2 ... print $2, $4 # 打印第 2、4 个字段 # AWK 自动维护许多辅助变量最重要的是 NF当前行的字段数 print NF # 打印当前行的字段数 print $NF # 打印当前行的最后一个字段 }模式本质上都是真值测试每个模式其实都是一个 true/false 测试。上面的正则模式也是只是隐藏了测试对象——如果没指定测试文本AWK 默认用 $0当前行。因此下面两种写法完全等价$0 ~ /^fobar$/ { 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.awkawk -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 tutorialgrymoire 的 Unix/Awk 教程从入门到进阶的经典系列Awk man pageman awk是查询内建变量、函数与语义的第一权威来源The GNU Awk Users GuideGNU/Linux 系统上最常见的 AWK 实现gawk的官方用户指南对实现细节与边界行为有最完整的说明。如果你已通读本文并动手跑通了平均年龄脚本就足以在日常日志分析、CSV 预处理、配置文件提取等场景中直接上手 AWK——毕竟它的全部设计目标就是让你少写代码、多做文本处理。赞分享文档教程【免费下载链接】learnxinyminutes-docsCode documentation written as code! How novel and totally my idea!项目地址https://gitcode.com/gh_mirrors/le/learnxinyminutes-docs点击查看免费下载相关推荐AWK 命令行文本处理实战指南模式-动作模型、内置函数与完整脚本learnxinyminutes-docs 详解AWK 命令行文本处理实战指南模式 动作模型、内置函数与完整脚本learnxinyminutes docs 详解 AWK 是每个 POSIX 兼容 UNI文档教程AWK 快速上手用模式与动作征服文本处理的实战指南learnxinyminutes-docsAWK 快速上手用模式与动作征服文本处理的实战指南learnxinyminutes docs AWK 是每个 POSIX 兼容 UNIX 系统上都自带的标文档教程learnxinyminutes-docs 中的 AWK 实战指南模式-动作编程、内置函数与文本处理全解析learnxinyminutes docs 中的 AWK 实战指南模式 动作编程、内置函数与文本处理全解析 本篇文章以 learnxinyminutes do文档教程上一篇Amplify JS aws-amplify/api 6.x 演进解析AmplifyContext 多租户/SSR 架构与 API 能力时间线下一篇换歌就自动找词揭秘foo_openlyrics自动搜索背后的playback回调机制创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进