grammars-v4 中 awk 示例脚本实战指南:用 AWK 对 CSV 文件进行字段检查、统计与清洗
2026/9/23 13:13:02 网站建设 项目流程

grammars-v4 中 awk 示例脚本实战指南:用 AWK 对 CSV 文件进行字段检查、统计与清洗

【免费下载链接】grammars-v4Grammars written for ANTLR v4; expectation that the grammars are free of actions.项目地址: https://gitcode.com/gh_mirrors/gr/grammars-v4

导读

本文围绕 grammars-v4 仓库 awk/examples/awk_scripts 目录下的 AWK 脚本集合展开,这是一套由 Uwe Geercken(datamelt.com)维护的、面向 CSV 类分隔文本文件的数据质量检查与清洗工具。读完本文,你将掌握 14 个即拿即用的 AWK 脚本的调用方式与参数约定,理解"字段定位—统计聚合—查询过滤—清洗转换"的完整 CSV 处理链路,并能根据脚本头部注释与源码结构快速定制自己的 AWK 数据处理流程。这些.awk文件同时还是仓库内 awk 语法(awkLexer.g4、awkParser.g4)的合法输入样例,因此也适合作为 ANTLR 语法验证与解析测试的数据来源。

脚本集合定位与使用前提

该目录是 grammars-v4 仓库中 awk 语法的示例资源之一。脚本集合的定位非常明确——README 开篇即说明:这是一组"对例如 CSV 文件执行特定检查"的 AWK 脚本,允许自由使用、修改与再发布;所有脚本都在开头附有使用说明注释。

在使用这套脚本前,需要了解两个关键前提:

  • 字段分隔符默认是分号;:尽管 README 与注释中通篇使用"CSV"一词,绝大多数脚本在BEGIN块中设置FS=";"(例如 field_query.awk、field_highlow.awk)。也就是说,它们面向的是分号分隔的表格数据,与常见逗号分隔 CSV 不同;直接套用逗号文件时需自行调整FS
  • 默认跳过表头行:除 header_fields.awk、number_of_fields_min_max.awk 等少数"全量统计"脚本外,多数脚本默认把第一行当作表头跳过,从第二行开始处理;若文件没有表头,需传入-v noheader=1

脚本均以#!/usr/bin/awk -f(或#!/usr/bin/gawk -f,如 field_permutations.awk、csv2redis.awk)开头,既可直接执行,也可用awk -f 脚本名.awk 参数 输入文件的方式调用。

贯穿全部脚本的通用参数约定

虽然每个脚本的参数不同,但整套脚本遵循高度一致的命名约定,理解这些约定后即可触类旁通:

参数(-v 传入)含义默认行为
fieldnumber指定要处理的字段/列序号(从 1 开始)多数脚本默认第 1 列;若为 0 或超出行的字段数,自动回退到第 1 列
fieldname指定字段名(配合表头行使用)脚本从表头行按名称(忽略大小写)反查列号
noheader=1声明输入文件无表头行默认认为有表头并从第 2 行开始处理
quiet=1仅输出核心结果,不输出统计摘要默认同时输出评估字段、行数等摘要信息
ignorecase=1忽略字段内容大小写(如Hellohello归并)默认区分大小写
nonumbers=1不显示计数(仅输出排列/匹配值本身)默认输出计数

以 field_permutations.awk 的注释为例,标准调用方式有两种:

# 按列号指定字段 awk -f field_permutations.awk -v fieldnumber=1 -v noheader=1 /home/test/datafile.csv # 按表头字段名指定字段,且只输出排列值本身 awk -f field_permutations.awk -v fieldname=lastname -v quiet=1 /home/test/datafile.csv

值得注意的实现细节是:当同时提供fieldnumber与表头行时,脚本会优先用fieldname在表头中查找列号;而列号回退逻辑(fieldnumber==0 || fieldnumber > NF时取第 1 列)写在与NR <= 1绑定的块中,即只对首行生效一次。

字段定位与文件结构概览

这类脚本用于回答"我的表有哪些列、某列在第几列、各行列数是否一致"等结构性问题,是后续所有按列处理工作的前置步骤。

header_fields.awk:输出表头字段与列位置

header_fields.awk 是最简单的脚本之一,仅对NR==1一行生效,用printf "%+24s %4i"将表头字段名右对齐到 24 字符宽、列号右对齐到 4 位输出:

awk -f header_fields.awk /home/test/datafile.csv

输出形如:

id 1 lastname 2 country 3

field_header_position.awk:按名称反查列号

field_header_position.awk 与上者互补——给定字段名,反查其所在列号。必须通过-v fieldname=...传入字段名,匹配时对表头内容做tolower()忽略大小写(源码 L32):

awk -f field_header_position.awk -v fieldname=country /home/testuser/testfile.csv

若未找到,输出column in header row: [not found]。在脚本头部注释给出的示例中,脚本自述文件名为field_query_header.awk,与实际文件名略有出入,可视为历史遗留的注释误差。

number_of_fields_min_max.awk:检查各行列数一致性

number_of_fields_min_max.awk 遍历所有记录,统计每行的字段数NF的最大值与最小值。这在数据质量检查中非常实用:若最大值与最小值不一致,说明文件中存在行间列数不齐的问题(如字段内混入了分隔符);反之则可以确认文件结构规整。其输出包括记录总数、字段数高值与低值,调用无需任何参数:

awk -f number_of_fields_min_max.awk /home/testuser/testfile.csv

统计与聚合:求和、极值、长度分布

field_sum.awk:对指定列求和

field_sum.awk 对指定列做数值累加,字符串值会被忽略(源码仅执行sum = sum + $fieldnumber,空值直接跳过)。fieldnumber为必传参数;若文件无表头需加noheader=1。脚本在BEGIN阶段把fieldname初始化为fieldnumber,随后在首行用NR <= 1 && !noheader条件块替换为真实列名,这是少数在条件中显式排除表头的脚本:

awk -f field_sum.awk -v fieldnumber=7 -v noheader=1 /home/testuser/testfile.csv

field_highlow.awk:输出指定列的最高/最低值

field_highlow.awk 输出指定列的高值与低值。注释明确说明"可以使用数字或字符串,字符串将按字母顺序排序"。实现上使用highestValue==nulllowestValue==null作为首次赋值哨兵,并对空值字段直接跳过;若传入ignorecase=1,比较前会先tolower字段内容:

awk -f field_highlow.awk -v fieldnumber=7 -v noheader=1 /home/testuser/testfile.csv

输出包含high valuelow value以及处理的总行数(不含表头)。

field_minmax_length.awk:输出指定列值的最短/最长长度

field_minmax_length.awk 统计指定列中值的最短与最长长度。脚本内置了一个trim()函数(用sub(/^ */)sub(/ *$/)去除首尾空格),统计的是去除首尾空格后的length();输出时同时给出对应的最短/最长样本值:

awk -f field_minmax_length.awk -v fieldnumber=7 -v noheader=1 /home/testuser/testfile.csv

输出形如:

minimum length: 2 : DE maximum length: 28 : United Kingdom of Great Britain

该脚本与 fields_trim.awk 中的trim()函数实现完全一致,可以推断这是该脚本作者在多个工具间复用的惯用函数片段。

排列与去重:字段值的唯一性分析

field_permutations.awk:单列值排列统计

field_permutations.awk 是这套脚本中功能最完整的代表之一,用于统计某一列出现了哪些不同的值("排列")及各自出现次数。它同时支持fieldnumberfieldname两种指定方式、noheaderquietignorecasenonumbers全套参数。核心实现是 AWK 的关联数组计数:++dimension[trim($fieldnumber)],空值统一记为[empty]。默认输出排列明细、排列总数、处理行数以及"排列数占总行数"的百分比:

awk -f field_permutations.awk -v fieldnumber=1 -v noheader=1 /home/test/datafile.csv

脚本中还保留了一段被注释掉的asorti()排序代码,并在注释中说明"数组也可以排序,但也可在 shell 中用 sort 命令完成"——即输出的排列顺序不保证有序,需要排序时可用管道交给sort处理。注意该脚本 shebang 为gawk,部分实现依赖 GNU awk 特性。

2field_permutations.awk:双列组合排列统计

2field_permutations.awk 是单列版本的扩展,统计两列值组合的排列情况,例如"国家 × 城市"的组合分布。必传参数为fieldnumber1fieldnumber2,两列各自在越界时回退到第 1 列;同样支持noheaderquietignorecasenonumbers。实现上以$fieldnumber1 " :: " $fieldnumber2作为关联数组键:

awk -f 2field_permutations.awk -v fieldnumber1=1 -v fieldnumber2=3 -v noheader=1 /home/test/datafile.csv

输出会先打印两列各自的字段名与列号(如evaluated fields: country(1) :: city(3)),再逐项输出组合与计数,最后给出排列总数与占比。

查询与过滤:按条件抽取数据行

field_query.awk:按正则匹配指定列筛选行

field_query.awk 根据指定列与查询字符串筛选数据行,其匹配基于 AWK 正则表达式规则(源码条件$fieldnumber ~ query)。fieldnumber必传,query为匹配模式;命中行通过print $0整行输出。quiet=1时仅输出命中行,否则附加评估字段、查询值、命中行数与总行数的摘要:

awk -f field_query.awk -v fieldnumber=7 -v query=Germany -v noheader=1 /home/testuser/testfile.csv

由于使用~正则匹配,query支持诸如^Ger[Gg]ermany等正则表达式,比精确字符串匹配灵活得多;但也意味着query中的正则元字符需要按正则规则转义,这一点与纯字符串匹配工具不同。

find_non_matching.awk:对照匹配文件找出未匹配/匹配值

find_non_matching.awk 实现"白名单/黑名单"式校验:读取一个匹配文件(每行一个词条,#开头或空行被忽略),然后在输入文件的指定列中查找等价值,最终输出未匹配的值及出现次数;传入-v reverse=1可反转逻辑,改为输出匹配的值。参数包括:

  • fieldnumber:指定列(缺省第 1 列);
  • matchingfile:匹配文件路径,必传,未指定时脚本打印错误并exit 1
  • noheader=1:输入文件无表头;
  • nonumbers=1:不显示出现次数;
  • reverse=1:反转匹配逻辑。
./find_non_matching.awk -v fieldnumber=4 -v matchingfile=match.txt inputfile.csv ./find_non_matching.awk -v reverse=1 -v fieldnumber=4 -v matchingfile=match.txt inputfile.csv

实现上,脚本在BEGIN阶段用getline将匹配文件读入关联数组(键统一tolower化,因此匹配不区分大小写),主循环用entry[tolower($fieldnumber)]查表,符合"校验数据是否符合预定义取值集合"的数据质量场景。结尾摘要会给出匹配文件词条数、输入总行数、找到的元素数以及匹配/未匹配行数。

清洗与转换:修正字段内容

fields_trim.awk:去除每个字段的首尾空格

fields_trim.awk 对输入每一行的所有字段执行首尾空格裁剪,并整行输出。BEGIN中同时设置FS=OFS=";",保证输出仍为分号分隔。内部trim()通过两次sub()正则替换完成(去开头空格^ *与结尾空格*$)。由于脚本没有表头跳过逻辑,它会原样处理所有行(包括表头),适合对整表做统一清洗:

awk -f fields_trim.awk inputfile.csv > outputfile.csv

capitalize_all_words.awk:单词首字母大写

capitalize_all_words.awk 将文本中的单词首字母大写、其余字符小写。其capitalizeWords()函数以空格切分文本,对每个词执行toupper(substr(word,1,1)) tolower(substr(word,2))后重组。脚本头部注释未列出参数,但源码中引用了一个exceptions变量(按逗号切分),可推断设计意图是支持"例外字段不转换";需要提醒的是,源码中存在一行print "xxx: " result;调试输出,且例外判断逻辑(result=exc[i]if(result=="")才转换)与注释意图并不完全吻合,可视为该脚本的已知瑕疵——使用者应结合自身数据验证输出,或按需修改后再用于生产。

replace_invalid_values.awk:用默认值替换空值/null 值

replace_invalid_values.awk 将不可用/不希望保留的空值统一替换为默认值,产出"不含任何空值或 null 值"的结果文件。脚本在BEGIN中定义DEFAULT_VALUE="[undefined]",对每一行的每个字段检查以下任一条件即替换:

  • 字符串"null""NULL""Null"(大小写变体);
  • 空字符串""或单个空格" "
  • AWK 的null值;
  • ~开头的值(正则$f ~ /^~/,推测用于标记"缺失/待定"类占位符)。
awk -f replace_invalid_values.awk inputfile.csv > outputfile.csv

替换逻辑写在普通主循环中,不区分表头与数据行;若希望表头也参与替换检查(如表头恰好是null),这一设计反而有利。

replace_matches.awk:按"键=值"映射表替换指定列

replace_matches.awk 实现字典式替换:先加载一个属性文件风格的替换表(每行键=值,如EUR=Europe),再将输入文件指定列中匹配键的值替换为对应的值。参数:

  • fieldnumber:要检查并替换的列号;
  • substitutionfile:替换表文件路径;
  • devider:键值分隔符,默认为=(可覆盖)。

脚本头部注释给出了完整的端到端示例:替换表subst.txtEUR=EuropeUSA=United States of AmericaMEA=Middle East,输入文件第 4 列含EURUSAAFR等值时,AFR(无映射)保持不变,EUR变为Europe

./replace_matches.awk -v fieldnumber=4 -v substitutionfile=subst.txt inputfile.csv

实现要点:BEGIN阶段用index()定位分隔符、substr()拆分键值并装入关联数组,以#开头的行视为注释忽略;主循环查表,命中则改写$fieldnumber,未命中则原样输出。脚本注释特别提醒两个陷阱:一是替换值中不要包含分隔符;,否则会产生行列数不一致的输出;二是替换表中的分隔符=不应出现在数据本身,否则键值拆分会出错。

进阶:csv2redis.awk——CSV 直灌 Redis

README.md 正文列出的 14 个脚本之外,目录中还存在一个未在清单中登记、但同样完整的 csv2redis.awk(2019-08-04 更新,作者邮箱为 uwe.geercken@web.de)。它把 CSV 数据转换为 Redis 协议格式,供redis-cli --pipe批量导入为 Redis Hash:

  • 前提:输入 CSV必须有表头行,表头列名将作为 Redis Hash 的字段名;
  • separator:列分隔符,默认逗号,(注意:这与其余脚本默认分号不同);
  • rediskey:Redis 键前缀,默认csvfile,实际键为rediskey:记录ID
  • uidcolumn:用作记录唯一 ID 的列号(从 1 开始),缺省使用行号;
  • 字符集问题:文件含éàöä等特殊字符时,建议给 awk 加-b标志。
awk -f csv2redis.awk csv_filename.csv awk -v separator="\t" -f csv2redis.awk csv_filename.csv awk -v uidcolumn=2 -v rediskey=myfile -f csv2redis.awk csv_filename.csv awk -b -f csv2redis.awk csv_filename.csv | redis-cli --pipe

其核心是toRedisProtocol()函数,按 RESP(REdis Serialization Protocol)手工拼装HMSET命令:*开头的参数个数行、$开头的长度前缀行,以及键、列名、列值序列。例如某行数据会被编码为*N\r\n$5\r\nHMSET\r\n...形式的命令流。由于输出直接就是协议字节流,配合redis-cli --pipe可避免逐条往返的网络开销。

与 awk 语法仓库的关联:示例即测试输入

这套脚本不仅是独立可用的工具集,还是仓库 awk 语法定义的有效输入样例。从 awk/desc.xml 的配置可以看到:

<targets>Antlr4ng;Cpp;CSharp;Dart;Go;Java;OphiRust;Python3;TypeScript</targets> <inputs>examples/**/*.awk</inputs>

examples/**/*.awk通配模式会匹配到awk_scripts/下的全部.awk文件(以及examples/根目录的 average_score.awk、count_words.awk、domain_name.awk 等),它们被用作该 ANTLR4 awk 语法的解析测试输入,目标覆盖 Antlr4ng、Cpp、CSharp、Dart、Go、Java、OphiRust、Python3、TypeScript 等运行时。换言之,这些脚本本身是被 awkLexer.g4 与 awkParser.g4 实际解析过的合法 awk 程序——这也是"脚本可以放心直接运行"之外的又一层保障。若你想验证某个改写后的脚本仍是合法 awk 语法,可通过仓库的构建/测试流程将其纳入语法解析验证。

使用注意事项与限制总结

最后,汇总这套脚本在实战中的注意事项,均以源码注释与实现为据:

  1. 分隔符不统一:绝大多数脚本默认FS=";",唯独 csv2redis.awk 默认逗号,且可通过separator覆盖。混用前务必确认输入文件的真实分隔符。
  2. 表头语义需显式声明:默认跳过第一行,无表头文件请传noheader=1;而 fields_trim.awk、replace_invalid_values.awk、number_of_fields_min_max.awk 等不区分表头,会处理所有行。
  3. 空值与大小写处理各有差异:排列类脚本将空值归一为[empty],查询/校验类脚本对空值直接跳过,替换类脚本对null/NULL/Null/空串/空格/~开头值统一替换;ignorecase=1仅部分脚本支持。
  4. 排序不保证:排列输出基于 AWK 关联数组遍历,顺序不定,需要排序时用管道交给sort
  5. 已知瑕疵:capitalize_all_words.awk 含有调试输出行且例外逻辑与注释不完全一致;field_header_position.awk 注释中的示例文件名与实际不符。使用前建议先在小样本上验证输出。
  6. 依赖差异:field_permutations.awk 与 csv2redis.awk 的 shebang 是gawk,若你的环境只有 mawk/BSD awk,部分写法可能需要适配。

总体而言,这套脚本把 CSV 类数据最常见的检查与清洗需求——结构摸底、字段定位、求和与极值、唯一值分布、正则筛选、白名单校验、空格裁剪、空值替换、字典映射、甚至直灌 Redis——都封装成了可独立运行的 AWK 单文件工具。它们既是随手可用的数据处理工具,也是学习"如何用 AWK 关联数组 + BEGIN/NR/NF/END 惯用法组织数据处理程序"的极佳参考实现。

【免费下载链接】grammars-v4Grammars written for ANTLR v4; expectation that the grammars are free of actions.项目地址: https://gitcode.com/gh_mirrors/gr/grammars-v4

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

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

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

立即咨询