在linux命令和一些文件比较,版本控制软件中,都会用到diff。它用来比较两个文本文件的差异,是代码版本管理的基石之一。

由于历史原因,diff有三种格式:

* 正常格式(normal diff)
* 上下文格式(context diff)
* 合并格式(unified diff)

在linux命令下比较两个文件的差异,用的是正常格式。比如我们创建一个五行都是一个a的文件test1.txt,然后复制它为test2.txt,并把第三行改为b。

diff test1.txt test2.txt

输出结果为正常格式的diff:

3c3
< a
---
> b

其中,第一行说明变动位置,第一个3说明test1.txt的第三行有变动,中间c表示文本变动,后面的3表示变动为test2.txt的第3行。
下面一行,前面<号去掉这一行,再接着是分隔线,接下来一行>号表示变动后加上这一行。

上个世纪80年代初,加州大学伯克利分校推出BSD版本的Unix时,觉得diff的显示结果太简单,因此加入上下文以了解变动情况,在今天的Linux中, 如果我们在上面的命令中加入参数c,也就是用

diff -c test1.txt test2.txt

则变成输出上下文格式的diff

*** test1.txt    2017-01-11 14:38:20.379006066 +0800
--- test2.txt    2017-01-11 14:19:20.303265901 +0800
***************
*** 1,5 ****
  a
  a
! a
  a
  a
--- 1,5 ----
  a
  a
! b
  a
  a

在连续15个*号之前,是文件的基本信息,***表示变动前的文件,—表示变动后的文件,两个文件变动的部分的上下文都列了出来。上下文一般是列出变动前后的三行,我们这里因为一共只有5行,所以上下文只列变动前后的两行。每一行前面有个标记位,如果为空,表示该行无变化;如果是感叹号(!),表示该行有改动;如果是减号(-),表示该行被删除;如果是加号(+),表示该行为新增。

如果两个文件相似度很高,那么上下文格式的diff,将显示大量重复的内容,很浪费空间。1990年,GNU diff率先推出了“合并格式”的diff。 在diff命令中,使用-u参数

diff -u test1.txt test2.txt

输出变成:

--- test1.txt    2017-01-11 14:38:20.379006066 +0800
+++ test2.txt    2017-01-11 14:19:20.303265901 +0800
@@ -1,5 +1,5 @@
 a
 a
-a
+b
 a
 a

可以看到,它保留了文件的基本信息,并把上下文合并了,用@@ -1,5 +1,5 @@ 这一行,-号表示第一个文件(或变动前文件)从第1行到第5行,+号则表示第二个文件(或变动后文件)的位置从第1行到第5行。在接下来的文件信息中,同样列出变动前后的三行,和前后的变动信息。每一行最前面的标志位,空表示无变动,减号表示第一个文件删除的行,加号表示第二个文件新增的行。

版本管理系统git,使用的是合并格式diff的变体。

mkdir test
cd test
git init
nano test
这里输入10行a

上面新建一个test目录,并初始化为git仓库目录,用创建一个test文件,输入10行a,保存

git add test
git commit
nano test
把第5行改为b
git diff

这时输出的就是git的diff了

diff --git a/test b/test
index d15112c..1a91ab8 100644
--- a/test
+++ b/test
@@ -2,7 +2,7 @@ a
 a
 a
 a
-a
+b
 a
 a
 a

第一行表示结果为git格式的diff。进行比较的是,a版本的test(即变动前)和b版本的test(即变动后)。 第二行表示两个版本的git哈希值(index区域的d15112c对象,与工作目录区域的1a91b8对象进行比较),最后的六位数字是对象的模式(普通文件,644权限)。“—“表示变动前的版本,”+++“表示变动后的版本。后面的行都与官方的合并格式diff相同。