3、Unix diff工具详解:diff命令的基本用法、上下文格式(-c)、统一格式(-u)、正常格式(-n)、递归比较目录(-r)
聊到差分补丁,diff 这个命令是绕不开的。我做了这么多年开发,几乎每天都在跟它打交道。说白了,diff 就是 Unix 世界里用来比较文件差异的瑞士军刀。你想想看,没有它,我们怎么知道代码改了啥?怎么生成补丁?
今天我就把 diff 的几种核心用法掰开揉碎了讲清楚。嗯,这里要注意,不同格式的 diff 输出,适用场景完全不同。选错了格式,队友可能看不懂你的补丁。
3.1 diff 命令的基本用法
diff 的语法其实很简单:
diff [选项] 文件1 文件2
它会逐行比较两个文件,然后输出差异。默认情况下,diff 使用「正常格式」(normal format)输出。我个人习惯先跑一遍不带参数的 diff,快速看看两个文件到底有没有区别。
来看个例子。假设我有两个文件:old.txt 和 new.txt。
old.txt
apple
banana
cherry
date
new.txt
apple
banana
cherry
elderberry
fig
执行 diff old.txt new.txt,输出是这样的:
4c4,5
< date
---
> elderberry
> fig
这个输出是什么意思?4c4,5 表示:文件1的第4行被改变(change)成了文件2的第4到5行。小于号 < 开头的是文件1的内容,大于号 > 开头的是文件2的内容。中间三个横线是分隔符。
基本用法虽然能用,但可读性确实差了点。尤其是当文件内容比较多的时候,你根本看不清上下文。所以,实际项目中我们很少用默认格式。
3.2 上下文格式(-c):看得更清楚
上下文格式是我早期用得最多的。它会在差异行周围显示几行上下文,让你知道改动发生在什么「语境」里。
执行 diff -c old.txt new.txt:
*** old.txt 2024-01-01 12:00:00.000000000 +0800
--- new.txt 2024-01-01 12:00:10.000000000 +0800
***************
*** 1,4 ****
apple
banana
cherry
! date
--- 1,5 ----
apple
banana
cherry
! elderberry
! fig
你看,输出分成了两部分:上面是文件1的信息,下面是文件2的信息。*** 1,4 **** 表示文件1的第1到4行,--- 1,5 ---- 表示文件2的第1到5行。感叹号 ! 标记了发生变化的行。
上下文格式默认显示3行上下文。你可以用 -c -n 来指定显示 n 行。比如 diff -c -5 old.txt new.txt 会显示5行上下文。
diff -c 的输出跟团队沟通变更范围。
3.3 统一格式(-u):最流行的选择
统一格式是现在最常用的 diff 格式。Git 的 git diff 默认就是这种格式。它把文件1和文件2的差异合并在一起,用加号 + 和减号 - 来标记新增和删除的行。
执行 diff -u old.txt new.txt:
--- old.txt 2024-01-01 12:00:00.000000000 +0800
+++ new.txt 2024-01-01 12:00:10.000000000 +0800
@@ -1,4 +1,5 @@
apple
banana
cherry
-date
+elderberry
+fig
这个输出比上下文格式简洁多了。@@ -1,4 +1,5 @@ 是块头(hunk header),表示文件1的第1到4行变成了文件2的第1到5行。减号开头的行是删除的,加号开头的行是新增的。
统一格式还有一个好处:它生成的补丁文件可以直接用 patch 命令应用。我建议你养成使用 -u 的习惯,尤其是在生成补丁的时候。
3.4 正常格式(-n):简洁但信息少
正常格式其实跟默认格式差不多,但输出更紧凑一些。它用 a(append)、c(change)、d(delete)来表示操作类型。
执行 diff -n old.txt new.txt:
d3 1
a4 2
elderberry
fig
d3 1 表示删除第3行的1行(date),a4 2 表示在第4行后面追加2行。这种格式非常简洁,但缺少上下文信息。说实话,我在实际项目中几乎没用过 -n。它更适合程序解析,而不是给人看的。
diff -n 来解析差异,结果因为输出太简洁,遇到空行和特殊字符时解析逻辑出了 bug。后来我换成了 diff -u 配合结构化解析,问题才解决。所以,除非你明确需要极简输出,否则别用 -n。
3.5 递归比较目录(-r):批量对比的利器
单个文件比较只是开胃菜。真正的大招是 diff -r,它可以递归比较整个目录树。这在对比两个版本的项目代码时特别有用。
假设你有两个目录:project_v1 和 project_v2。执行:
diff -r project_v1 project_v2
它会递归遍历两个目录下的所有文件,逐一比较。输出会告诉你哪些文件有差异、差异在哪里。
我经常把 -r 和 -u 组合使用:
diff -ru project_v1 project_v2
这样就能得到整个项目的统一格式差异输出。如果你只想看哪些文件有变化,不想看具体内容,可以加上 -q(quiet)选项:
diff -rq project_v1 project_v2
输出会简洁很多,只列出有差异的文件名。
还有一个实用技巧:用 -x 或 --exclude 排除不需要比较的目录或文件。比如排除 node_modules:
diff -ru project_v1 project_v2 -x node_modules
diff -ruq 对比当前代码和上一个发布版本,快速确认改动了哪些文件。这个习惯帮我避免过好几次漏提交的问题。
3.6 四种格式对比一览
为了方便你快速选择,我把四种格式整理成了表格:
| 格式 | 选项 | 可读性 | 上下文信息 | 适用场景 |
|---|---|---|---|---|
| 正常格式 | (默认) | 低 | 无 | 快速查看差异 |
| 上下文格式 | -c |
高 | 有(默认3行) | 代码审查、人工阅读 |
| 统一格式 | -u |
高 | 有(默认3行) | 生成补丁、Git diff |
| 正常格式(精简) | -n |
极低 | 无 | 程序解析 |
3.7 知识体系图
下面这张 SVG 图帮你梳理了本章的核心知识点:
这张图把 diff 的四种核心用法和它们的子特性都串起来了。你可以把它当作一个快速参考,需要的时候瞄一眼就知道该用哪个选项。
好了,关于 diff 命令的基本用法和几种格式,我就讲到这里。记住,实际工作中 -u 和 -r 是你最常用的两个选项。多练练,很快就能上手。