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 | 忽略字段内容大小写(如Hello与hello归并) | 默认区分大小写 |
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 3field_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.csvfield_highlow.awk:输出指定列的最高/最低值
field_highlow.awk 输出指定列的高值与低值。注释明确说明"可以使用数字或字符串,字符串将按字母顺序排序"。实现上使用highestValue==null与lowestValue==null作为首次赋值哨兵,并对空值字段直接跳过;若传入ignorecase=1,比较前会先tolower字段内容:
awk -f field_highlow.awk -v fieldnumber=7 -v noheader=1 /home/testuser/testfile.csv输出包含high value、low 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 是这套脚本中功能最完整的代表之一,用于统计某一列出现了哪些不同的值("排列")及各自出现次数。它同时支持fieldnumber与fieldname两种指定方式、noheader、quiet、ignorecase、nonumbers全套参数。核心实现是 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 是单列版本的扩展,统计两列值组合的排列情况,例如"国家 × 城市"的组合分布。必传参数为fieldnumber1与fieldnumber2,两列各自在越界时回退到第 1 列;同样支持noheader、quiet、ignorecase、nonumbers。实现上以$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.csvcapitalize_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.txt含EUR=Europe、USA=United States of America、MEA=Middle East,输入文件第 4 列含EUR、USA、AFR等值时,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 语法,可通过仓库的构建/测试流程将其纳入语法解析验证。
使用注意事项与限制总结
最后,汇总这套脚本在实战中的注意事项,均以源码注释与实现为据:
- 分隔符不统一:绝大多数脚本默认
FS=";",唯独 csv2redis.awk 默认逗号,且可通过separator覆盖。混用前务必确认输入文件的真实分隔符。 - 表头语义需显式声明:默认跳过第一行,无表头文件请传
noheader=1;而 fields_trim.awk、replace_invalid_values.awk、number_of_fields_min_max.awk 等不区分表头,会处理所有行。 - 空值与大小写处理各有差异:排列类脚本将空值归一为
[empty],查询/校验类脚本对空值直接跳过,替换类脚本对null/NULL/Null/空串/空格/~开头值统一替换;ignorecase=1仅部分脚本支持。 - 排序不保证:排列输出基于 AWK 关联数组遍历,顺序不定,需要排序时用管道交给
sort。 - 已知瑕疵:capitalize_all_words.awk 含有调试输出行且例外逻辑与注释不完全一致;field_header_position.awk 注释中的示例文件名与实际不符。使用前建议先在小样本上验证输出。
- 依赖差异: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),仅供参考