如何比较两个 CSV 文件并查看改动内容
比较两个 CSV 文件最快的方法,是把两者都粘贴进一个并排显示的 diff 工具, 对齐各列,然后阅读它高亮出的行。比较本身是简单的部分。 真正坑人的是噪音:一次重新排序的导出、一个用分号代替逗号的分隔符, 或者某人给某个值加上的引号,都会让两个承载相同数据的文件看起来毫无共同之处。
本指南会讲解如何得到干净、可信的 CSV diff。我们会看看两份等价的导出文件 为什么在字面上渐行渐远,有哪些方法值得了解,以及一个你可以跟着做的完整实例。 如果你只想要工具,我们的 CSV 比较页面可以在浏览器里完成这件事。
为什么 CSV 文件比看上去更难比较
CSV 看起来简单,但它没有唯一的严格标准。最接近标准的是 RFC 4180, 而现实世界里大量文件并不遵循它。同一张表的两次导出, 可能在分隔符、引号用法、行尾符和行顺序上都不同, 却描述着完全相同的数据。纯文本 diff 对此一无所知,于是会把这些全部标记出来。
开始之前有一个关键问题需要先决定:行顺序对你来说重要吗? 一份按姓名排序的数据库导出和同一份按 ID 排序的导出, 承载着完全相同的记录集合,但按行 diff 会把几乎每一行都涂成红色和绿色。 如果这些行有稳定的键(比如一个 ID 列), 先按该键对两个文件排序,就能让 diff 重新变得可读。
| 你在 diff 里看到的 | 是真正的改动吗? | 该怎么做 |
|---|---|---|
| 行的顺序不同 | 不是,只要数据是同一个集合 | 按某个键列对两个文件排序 |
| 逗号 vs 分号分隔符 | 不是,字段相同 | 统一归一化成一种分隔符 |
Ada vs "Ada" | 不是,这里引号是可选的 | 统一引号用法 |
| CRLF vs LF 行尾符 | 不是 | 归一化行尾符 |
| 末尾多出的空行 | 不是 | 去掉它 |
| 某个单元格的值变了 | 是 | 需要排查,这是真改动 |
分隔符那一行坑到过很多人,跨地区时尤其如此: 许多欧洲区域设置使用分号,因为逗号在那里是小数点分隔符。 数据相同,分隔符不同。如果你想了解浏览器和工具是如何读取表格文本的, MDN 上关于 在浏览器中读取文件 的说明是个有用的起点。
比较 CSV 的四种方法,以及各自适用的场合
没有哪一种方法是绝对最好的。这取决于文件的大小,以及行顺序是否重要。 下面是几种常见选项的对比。
| 方法 | 适合 | 投入 | 理解 CSV 吗? |
|---|---|---|---|
| 用肉眼看 | 很小的文件,寥寥几行 | 低 | 不,你就是解析器 |
| 在线 diff 工具 | 快速检查,随处粘贴 | 低 | 逐行来看,可以 |
| 电子表格软件 | 可视化审查、公式、筛选 | 中 | 可以,但需手动操作 |
命令行(sort、csvkit) | 大文件、脚本、按键比较 | 中 | 先排序的话,可以 |
对大多数人来说,浏览器工具在速度上胜出:无需安装, 而且你可以直接从下载文件或数据库客户端里粘贴一份导出内容。 代价是行顺序和分隔符噪音,我们接下来就处理它。对于非常大的文件, 命令行上的 csvkit 值得学一学。
最快得到干净比较结果的步骤
每当有人扔给我两份导出文件并问"有什么不一样?"时,我就是这么做的。 不到一分钟就能完成。
- 确认两个文件使用相同的分隔符,并且有相同的表头行。
- 如果行顺序无关紧要,先按某个键列对两个文件排序。
- 打开 CSV 比较工具。
- 把原始版本粘贴到左侧,新版本粘贴到右侧。
- 阅读结果。绿色表示新增的行,红色表示删除的行,而改动的单元格会各显示一次。
当行是无序的时候,第二步就是全部诀窍。一旦两个文件按同样方式排好序, 剩下被高亮出来的就只有真正改动的行了。我们的 diff 引擎构建在 Google 的 diff-match-patch 之上,它会先逐行比较,所以即使文件很长也依然很快。
一个完整实例
假设你正在审查一处对用户表的改动。这是改动前:
id,name,role,seats
7,Ada Lovelace,editor,3
8,Alan Turing,viewer,1
这是改动后的版本,导出时行顺序不同:
id,name,role,seats
8,Alan Turing,viewer,1
7,Ada Lovelace,admin,5
把它们丢进原始的按行 diff,两行数据看起来都变了,因为它们交换了位置。
按 id 列对两者排序之后,真实情况其实很简单:
| 行(按 id) | 列 | 改动前 | 改动后 | 改动 |
|---|---|---|---|---|
| 7 | role | editor | admin | 已修改 |
| 7 | seats | 3 | 5 | 已修改 |
| 8 | — | — | — | 无改动(只是移动了位置) |
一处真实修改:Ada 的角色和席位数变了。Alan 那一行只是移动了位置。
从 editor 提升到 admin
正是你在审查时想要抓住的那类改动,而当它被埋在一堆被 diff 误判为改动的行之下时,
很容易被漏掉。
在命令行上消除行顺序噪音
如果文件已经在磁盘上、而且这些行本身没有固有顺序, 同样的"先排序"思路用两条简短的命令就能实现。保留表头,只对其余部分排序:
(head -1 old.csv; tail -n +2 old.csv | sort) > old.sorted.csv
(head -1 new.csv; tail -n +2 new.csv | sort) > new.sorted.csv
diff old.sorted.csv new.sorted.csv
现在 diff 只会报告真正改动的行,因为两个文件顺序一致。
如果要做同时忽略列顺序的按键比较,csvkit 的 csvsort 和
csvjoin 能给你更多控制。这就是在浏览器里比较前先对两侧排序的
终端等价操作。
分隔符、引号,以及混乱的现实
真实的 CSV 文件时时刻刻都在破坏规则。含有逗号的字段必须加引号,
所以 "Lovelace, Ada" 是一个字段,而不是两个。
带引号字段内部的引号要写两次:"She said ""hi"""。
而分隔符本身也各不相同:在以逗号作小数点的区域设置里,
电子表格导出时会用分号,而制表符分隔的文件(TSV)在数据管道中也很常见。
比较之前,先确认两个文件使用相同的分隔符和相同的引号约定,
或者用一个遵循 RFC 4180 的解析器把它们归一化。
否则这个 diff 比较的就是两种不同的方言,而不是同一份数据的两个版本。
需要留意的常见陷阱
| 陷阱 | 为什么会出问题 | 解决办法 |
|---|---|---|
| 分隔符不匹配 | 逗号 vs 分号会让每个字段看起来都不同 | 先统一归一化成一种分隔符 |
| 开头的 BOM | 隐藏的字节顺序标记会改变第一个表头单元格 | 比较前去掉 BOM |
| 带前导零的 ID | 电子表格可能已经把 007 变成了 7 | 把 ID 列保持为文本;按字符串比较 |
| 字段内嵌的换行 | 带引号的字段可能含有换行,导致之后每一行都错位 | 使用真正的 CSV 解析器,而不是按行切分 |
| 单元格里的行尾空格 | 看不见的空格会显示为改动 | 比较前裁剪单元格的值 |
什么时候电子表格是更好的工具
文本 diff 非常适合看清哪些行变了、以及把改动当作数据来审查。 但如果你需要在数千行上做筛选、透视,或者用公式来比较, 电子表格更合适:导入两个文件,按键对齐,再用查找函数标出不匹配之处。 这两种方式是互补的。快速直观地看一眼时用 diff,需要切分数据时用电子表格。 无论走哪条路,关于底层格式规则, Wikipedia 的 CSV 概述 都是一份扎实的参考。
相关工具
CSV 很少是你要处理的唯一格式。如果同一份数据也以 JSON 形式存在, JSON 比较用的是同样的思路。 给某一列的值去重,用 删除重复行很快就能搞定, 而在做 diff 之前把行整理成可预期的顺序,正是 行排序的用途。
常见问题
- 在线比较 CSV 文件会把它们上传到什么地方吗?
- 在 comparetext.org 上,diff 在你的浏览器里运行。两个 CSV 文件由你自己机器上的 JavaScript 完成比较,所以除非你明确点击保存或分享,否则不会有任何内容被发送到服务器。这让它可以安全地用于客户数据导出、财务数据,以及其他你不愿粘贴到某个每敲一次键就上传一次的网站上的表格。
- 为什么我的两个 CSV 文件每一行都显示为不同?
- 几乎总是行顺序或分隔符的问题,而不是真正的改动。可能一份导出的排序方式与另一份不同,或者一个用逗号、另一个用分号,也可能是行尾符不一致。先确认两个文件使用相同的分隔符,然后按某个键列对两者排序,让顺序不再有影响。这之后,diff 通常会缩小到少数几行和几个真正改变的单元格。
- 我该如何在比较两个 CSV 文件时忽略行的顺序?
- 比较前先按一个稳定的键列对两个文件排序。如果这些行有 ID,就按 ID 排序;否则就对整行排序。保留表头行不动,只对数据行排序。在浏览器里,你可以粘贴排序后的版本;在命令行上,则对表头之后的部分排序。一旦两个文件顺序一致,diff 里显示出来的就只有真正改动的行,而不是每一行仅仅因为移动了位置就被标出。
- 为什么我的 CSV 用的是分号而不是逗号?
- 许多欧洲区域设置把逗号用作小数点分隔符,所以电子表格软件导出 CSV 时会改用分号,以避免歧义。数据是一样的,只有分隔符不同。比较两个文件之前,先确认它们用的是相同的分隔符,否则你会看到每个字段都被标记为已改动。用一个理解 CSV 的工具把两者都归一化成逗号(或都归一化成分号)即可解决。制表符分隔的文件也有同样的问题,只是分隔符不同。
- 列顺序不同的 CSV 文件该怎么比较?
- 纯文本 diff 是从左到右逐行比较的,所以即便数据一致,重新排列的列也会看起来像是全变了。要处理这种情况,可以先把两个文件的列调整成相同顺序,或者使用一个按表头名称而非位置来比较的、理解 CSV 的工具。csvkit 这类命令行工具可以按名称选取和重排列。一旦各列顺序对齐,普通的逐行 diff 就又能用了。
- 我能比较很大的 CSV 文件而不让页面卡死吗?
- 可以,但有限度。按行模式的 diff 在数千行的文件上依然很快,因为它先比较整行而不是每个字符。非常大的文件(几十 MB 或数百万行)更适合用 csvkit 这类命令行工具或数据库导入来处理,它们会流式读取数据。只要是你能在浏览器里舒服滚动浏览的导出内容,在线 diff 就是更快的选择。
准备好试试了吗?把你的文件粘贴进 CSV 比较工具,看看改动了什么。