21 文本处理

把日志、数据、代码和配置的批量检查拆成可复核的文本管线:先解析,再筛选、变换,最后用抽样和总量校验交付。

学习目标

  • 能把一批异构文本拆成文本源、解析、筛选、变换和校验五个可交接节点
  • 能修改一个匹配边界,报告被接受、被拒绝和无法解析的输入,并保留失败原因
  • 能回答:为什么批量脚本必须用抽样结果与总量守恒共同证明自己没有静默丢行?

为什么这一单元不可压缩

日志、配置、CSV 和代码看起来都只是“几行文字”,但它们的分隔符、缺失字段和转义规则各不相同。把它们塞进一条长命令,短期省下几分钟,长期却很难知道哪一行被误读、哪一种输入被跳过。

本单元把文本工作拆成一条可重放的管线:先保留原始输入,再明确结构和边界,接着批量处理,最后对样本、总量和拒绝项做验收。读完应能把一次“批量替换成功”改写成另一位工程师可以复查的证据包。

先看问题:一行文字如何变成可信结果

想象一批部署记录:正常行包含时间、服务名和状态,旧版本却偶尔留下缺列行。如果脚本只输出“处理完成”,你无法区分“没有异常”和“异常被静默丢弃”。可靠的脚本必须让每次交接都能回答:收到了什么、识别成什么、为什么接受或拒绝、交给谁、如何验证。

先预测:如果删除“只匹配状态字段”的边界规则,首个异常应该出现在解析、筛选还是最终报告?打开实验台,先注入故障,再逐步推进;观察首差是否和你的预测一致。

常见误区

21 文本处理:把文字变成可复核的数据流

的重点不是某个命令的长短,而是每次交接都保留可解释的状态。输入可以是日志、数据文件、代码或配置;输出也不必是新文件,可能是一份报告、一个补丁或一组拒绝记录。

本章的主链只有五站: 先回答“这行由什么组成”, 再回答“哪一部分算命中”, 才能安全扩展规模, 最后证明输出没有悄悄偏离输入。

先把专业判断和机械重复分开:人应该决定字段语义、允许的缺失和拒绝策略;脚本应该稳定地遍历输入、执行规则、保留证据。若同一批输入再运行一次仍得到同样结果,这种稳定性就是重复运行不再改变结果的可检验信号。

21 文本处理:先交接结构,再批量变换文本源 → 解析 → 筛选 → 变换 → 校验:每个节点都能拒绝不合格输入1文本源log / CSV / code交付:保留原始行输入与输出可记录2解析字段与记录交付:结构化对象输入与输出可记录3筛选边界与匹配交付:接受或拒绝输入与输出可记录4变换map / replace交付:批量结果输入与输出可记录5校验抽样与总量交付:可交付报告输入与输出可记录验收合同:每次交接都留下原文、结构、决定、结果与校验批量不等于盲目循环:无法解析的输入必须单独报告,不能静默丢弃先保存无法解析的输入,再谈速度;结果校验必须能重放

第 1 / 5 步 · 1. 文本源:保留原始行

沿着五个交接点观察:原始文本何时变成结构,规则何时决定接受或拒绝。

文本处理的速度来自清晰的交接合同,而不是把解析、筛选和变换揉成一行脚本。

可重放与幂等:让重复运行仍然可比较

把同一批输入再次交给脚本,若输出不因时间、遍历顺序或临时文件而漂移,就得到 性的证据。它不是“永远没有错误”,而是每次错误都能落到同一份输入、规则版本和拒绝原因上。

提示35:学习一门文本处理语言

“提示35:学习一门文本处理语言”不是要求背下一套命令,而是要求选择一种能表达读取、匹配、变换和报告的工具,并为它建立可重放的边界。工具可以是 shell、正则库、脚本语言或项目内的专用解析器;选择依据是输入格式、失败可见性、批量规模和团队能否维护规则。

本章五节点把这条提示变成验收动作:先用工具保留原文,再用结构解析确认字段,用正则边界筛选,批处理执行变换,最后用结果校验证明接受数、拒绝数和重跑结果都可解释。工具越短,越要把拒绝原因和规则版本写进报告。

文本源与结构解析:先保留原文,再建立字段

文本源是管线的证据起点。读取文件时为每条记录保存来源文件、行号和原始文本;解析成功后再附加字段对象。这样,后续报告即使发现字段不对,也能回到原始行复核,而不是凭一段已经被清洗过的字符串猜测。

解析器要明确“合法输入”和“无法解析”是两种不同结果。CSV 的逗号可能出现在引号内,日志的空格可能只是消息的一部分,配置文件还可能允许注释或转义。不要把所有格式都压成同一个 split(" ");先列出格式合同,再为每个合同写正常样本、边界样本和拒绝样本。

正则边界:匹配成功不是语义成功

正则表达式适合描述局部形状,例如“状态字段是 WARNERROR”,但一个子串命中不等于整条记录符合业务条件。把字段边界、行首尾和上下文写进规则,并保留捕获字段;规则越宽,误报越难在结果阶段补救。

const record =
  /^(?<time>\S+)\s+(?<service>[a-z-]+)\s+(?<level>WARN|ERROR)\s+(?<message>.*)$/;
const match = record.exec(line);
if (!match?.groups) rejected.push({ lineNumber, reason: "格式或状态不符合" });
else accepted.push({ ...match.groups, lineNumber });

这里的首要合同不是“表达式能匹配”,而是“每个分支都能解释”。accepted 表示整行满足边界,rejected 表示输入仍然存在但不能进入下游;两者不可合并,否则后面的计数无法回答到底是没有命中,还是解析失败。

批处理:把重复动作变成有边界的循环

批处理的价值是让同一规则覆盖许多文件,而不是允许脚本把错误扩大许多倍。处理每项时保留输入身份、规则版本和输出状态;单项失败要记录并继续或停止,取决于需求合同,但选择必须显式写下。对于会改写文件的脚本,先写到临时输出,再在校验通过后替换,避免半写入文件伪装成成功结果。

一次批处理至少要有三个计数:读入数、接受数和拒绝数。若还有重复、覆盖、空文件或解析错误,再单独计数。总量不一定简单相加,但每一项都必须能落到“接受、拒绝、未处理”中的一个桶里;无法归类就是报告本身的缺口。

结果校验:样本与总量共同验收

校验不是把最后一行打印出来看看。先固定一组确定性样本,核对字段和变换结果;再核对输入身份、读入总数、输出总数、拒绝数和错误列表。对于可重跑的脚本,使用相同输入再次运行,比较输出摘要或内容哈希,确认没有依赖时间、遍历顺序或残留临时文件。

若输出是代码或配置补丁,还要检查变更边界:只改了允许的字段,未匹配的行保持不变,语法检查和下游测试都通过。若有任何输入被跳过,报告必须能给出行号、原因和恢复动作;“成功处理 97%”不是结论,除非剩下的 3% 有明确去处。

代码对照:先解析,再筛选和变换

把三个动作拆开,读者可以独立测试每个边界;把它们揉成一个正则替换,出错时只剩一条无法定位的长表达式。下面的伪代码保留拒绝项,并让结果校验拥有自己的输入。

const accepted = [];
const rejected = [];
for (const item of inputs) {
  const parsed = parseRecord(item);
  if (!parsed.ok) {
    rejected.push({ source: item.source, reason: parsed.reason });
    continue;
  }
  if (!matchesBoundary(parsed.value)) {
    rejected.push({ source: item.source, reason: "边界未命中" });
    continue;
  }
  accepted.push(transform(parsed.value));
}
return verify({ accepted, rejected, inputCount: inputs.length });

这段结构把“无法解析”和“合法但未命中”分开,也为单元测试留下三个清晰入口:解析器、边界规则和最终校验。它不是某个语言的完整框架;在真实项目中仍需补上编码、权限、超时、资源上限和敏感数据脱敏策略。

三步观察:从原文走到可交付报告

每一步都使用同一批固定输入。动手前先写出预计的首差;图中的高亮表示当前教学位置,不替你证明真实文件已经满足合同。

分步1 / 3

1. 固定文本源并解析

保存来源、行号和原文,选择正常、缺列和带转义字符的样本。先确认无法解析的行会进入拒绝列表,而不是变成空结果。

21 文本处理:先交接结构,再批量变换文本源 → 解析 → 筛选 → 变换 → 校验:每个节点都能拒绝不合格输入1文本源log / CSV / code交付:保留原始行输入与输出可记录2解析字段与记录交付:结构化对象输入与输出可记录3筛选边界与匹配交付:接受或拒绝输入与输出可记录4变换map / replace交付:批量结果输入与输出可记录5校验抽样与总量交付:可交付报告输入与输出可记录验收合同:每次交接都留下原文、结构、决定、结果与校验批量不等于盲目循环:无法解析的输入必须单独报告,不能静默丢弃先保存无法解析的输入,再谈速度;结果校验必须能重放

第 1 / 5 步 · 1. 文本源:保留原始行

沿着五个交接点观察:原始文本何时变成结构,规则何时决定接受或拒绝。

文本处理的速度来自清晰的交接合同,而不是把解析、筛选和变换揉成一行脚本。

运行实验:首差必须可见

猜一猜:删除正则边界后,文本管线会在解析、筛选还是最终校验处先拒绝?实验台只注入一个条件变化;点击“注入单故障”,再逐步推进,最后点击“重置实验台”确认故障状态和首节点都恢复。

提示35:学习一门文本处理语言 · 单故障实验台
21 文本处理:先交接结构,再批量变换文本源 → 解析 → 筛选 → 变换 → 校验:每个节点都能拒绝不合格输入1文本源log / CSV / code交付:保留原始行输入与输出可记录2解析字段与记录交付:结构化对象输入与输出可记录3筛选边界与匹配交付:接受或拒绝输入与输出可记录4变换map / replace交付:批量结果输入与输出可记录5校验抽样与总量交付:可交付报告输入与输出可记录验收合同:每次交接都留下原文、结构、决定、结果与校验批量不等于盲目循环:无法解析的输入必须单独报告,不能静默丢弃先保存无法解析的输入,再谈速度;结果校验必须能重放

第 1 / 5 步:文本源 已交付 保留原始行。

第 1 / 5 步 · 1. 文本源:保留原始行

先预测首差,再注入故障;重置后应回到完整输入、第一节点和未注入状态。

删除边界规则不会让批处理更快,只会把无法解释的行偷偷推向下游。

决策账本:让批量结果能被重放

字段必答问题拒绝条件
输入哪些文件、编码、版本和行号范围被冻结?只有“读取目录”描述,没有输入身份
解析每种格式的字段、转义和缺失规则是什么?合法空值与解析失败共用一个结果
边界什么条件让一条记录接受或拒绝?只有“正则命中”,没有整行或字段边界
变换哪些字段允许修改,失败时如何回退?直接覆盖原文件,无法比较前后差异
校验样本、总量、拒绝列表和重跑结果在哪里?只看一行输出或只看进程退出码

记录拒绝项并不会降低自动化价值,反而把自动化的边界暴露出来。若业务确认某类旧格式合法,就更新格式合同、样本和测试;不要为了让总数看起来漂亮而删除失败记录。

本章回顾

  • 文本源保留原文、来源和行号,解析后才产生字段。
  • 正则边界定义接受与拒绝,命中子串不等于整行合法。
  • 批处理必须分开接受、拒绝和无法解析的输入。
  • 结果校验同时检查确定性样本、总量、拒绝列表和重跑稳定性。
  • 幂等与可重放让一次脚本运行成为团队可复查的证据。

完成本章后,你应能把一批异构文本改写成五节点管线,指出首个不合格交接,并留下另一位工程师可以重建的输入、规则版本和验收结果。

练习:把批量脚本改成可检验管线

练习

问题 1:设计拒绝报告。 一批日志有三种输入:完整行、缺少服务名的行、消息中包含空格的行。请写出解析成功、边界未命中和无法解析三种结果应分别保存的字段。

问题 2:改 Demo 代码。 用“提示35:学习一门文本处理语言”的要求,在实验台已有的“删除正则边界”故障之外,设计一个只改变一个条件的故障,例如把 ERROR 边界改成只接受 WARN。请写出预测的首差、拒绝原因和重置后的状态。

问题 3:补齐批量验收。 脚本报告“读取 1000 行,输出 970 行”,但没有拒绝列表。请写出至少四项必须补充的验收证据,并说明为什么只看输出样本不够。

名词解释

名词解释

本章出现的专业名词,用大白话再讲一遍。

文本处理

用程序读取、判断、修改并检查文字资料,而不是手工逐行处理。

结构解析

按格式规则把一行文字拆成程序能检查的字段和记录。

正则边界

规定匹配从哪里开始、到哪里结束以及哪些上下文必须同时满足的条件。

批处理

用同一套可记录的规则处理一批文件或记录,并保留每项结果。

结果校验

用样本、计数、拒绝列表和重跑比较检查输出是否符合约定。

幂等

同一操作重复执行时,不会因为重复运行而继续改变已经生成的结果。

来源与改写范围

前后导航

讨论

评论区加载中…