如何比较两个 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 工具快速检查,随处粘贴逐行来看,可以
电子表格软件可视化审查、公式、筛选可以,但需手动操作
命令行(sortcsvkit大文件、脚本、按键比较先排序的话,可以

对大多数人来说,浏览器工具在速度上胜出:无需安装, 而且你可以直接从下载文件或数据库客户端里粘贴一份导出内容。 代价是行顺序和分隔符噪音,我们接下来就处理它。对于非常大的文件, 命令行上的 csvkit 值得学一学。

最快得到干净比较结果的步骤

每当有人扔给我两份导出文件并问"有什么不一样?"时,我就是这么做的。 不到一分钟就能完成。

  1. 确认两个文件使用相同的分隔符,并且有相同的表头行。
  2. 如果行顺序无关紧要,先按某个键列对两个文件排序。
  3. 打开 CSV 比较工具
  4. 把原始版本粘贴到左侧,新版本粘贴到右侧。
  5. 阅读结果。绿色表示新增的行,红色表示删除的行,而改动的单元格会各显示一次。

当行是无序的时候,第二步就是全部诀窍。一旦两个文件按同样方式排好序, 剩下被高亮出来的就只有真正改动的行了。我们的 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)改动前改动后改动
7roleeditoradmin已修改
7seats35已修改
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 的 csvsortcsvjoin 能给你更多控制。这就是在浏览器里比较前先对两侧排序的 终端等价操作。

分隔符、引号,以及混乱的现实

真实的 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 比较工具,看看改动了什么。