# diff & patch

### **摘要**

在 Linux 的日常使用中，我们经常需要修改一些配置文件，然而在软件升级以后，经常会面临配置更新后与原配置部分不兼容的问题（当然我们更多的可能是来制作软件升级的补丁）。在这种情况下我们通常有两种选择：

* 对比现有配置，手动在新配置文件中改动
* 利用 `sed`、`awk` 等工具配合改动
* 采用 `diff` 与 `patch` 制作增量补丁的方式改动

本文主要通过一个升级`awesome` 配置的例子，对第三种方法进行介绍和讲解。

### **diff 介绍**

`diff` 是一个文件比较工具，可以逐行比较两个文件的不同，其中它有三种输出方式，分别是 `normal`， `context` 以及 `unified`。区别如下：

* `normal` 方式为默认输出方式，不需要加任何参数
* `context` 相较于 `normal` 模式的简单输出，`contetx` 模式会输出修改过部分的上下文，默认是前后 **3** 行。使用参数 `-c`
* `unified` 合并上下文模式则为新的上下文输出模式，同样为前后 **3** 行，只不过把上下文合并了显示了。使用参数 `-u`

**注**：本文主要介绍 `unified` 模式

其他常用参数：

* `-r` 递归处理目录
* `-N` 将缺失的文件当作空白文件处理

### `diff 语法与文件格式`

```
diff [options] old new
```

先来看一个简单的例子：

```
$ cat test1 
linux
linux
linux
linux

$ cat test2
locez
linux
locez
linux
```

此时输入 `diff -urN test1 test2` 会输出以下信息：

```
--- test1   2018-05-12 18:39:41.508375114 +0800
+++ test2   2018-05-12 18:41:00.124031736 +0800
@@ -1,4 +1,4 @@
+locez
 linux
-linux
-linux
+locez
 linux
```

先看前面 2 行，这两行为文件的基本信息，`---` 开头为改变前的文件，`+++` 开头为更新后的文件。

```
--- test1   2018-05-12 18:39:41.508375114 +0800
+++ test2   2018-05-12 18:41:00.124031736 +0800
```

第三行为上下文描述块，其中 `-1,4` 为旧文件的 4 行上下文，`+1,4` 为新文件的：

```
@@ -1,4 +1,4 @@
```

而具体到块里面的内容，前面有 `-` 号的则为删除，有 `+` 号为新增，不带符号则未做改变，仅仅是上下文输出。

### **patch 介绍**

`patch` 是一个可以将 `diff` 生成的**补丁**应用到源文件，生成一个打过补丁版本的文件。语法：

```
patch [oiption] [originalfile [patchfile]]
```

常用参数：

* `-i` 指定补丁文件
* `-pNum` 在 `diff` 生成的补丁中，第一二行是文件信息，其中文件名是可以包含路径的，例如 `--- /tmp/test1 2018-05-12 18:39:41.508375114 +0800` 其中 `-p0` 代表完整的路径 `/tmp/test1`，而 `-p1` 则指 `tmp/test1`，`-pN` 依此类推
* `-E` 删除应用补丁后为空文件的文件
* `-o` 输出到一个文件而不是直接覆盖文件

### **应用**

awesome 桌面 3.5 与 4.0 之间的升级是不兼容的，所以在升级完 4.0 以后，awesome 桌面部分功能无法使用，因此需要迁移到新配置，接下来则应用 `diff` 与 `patch` 实现迁移，当然你也可以单纯使用 `diff` 找出不同，然后手动修改新配置。

现在有以下几个文件：

* `rc.lua.3.5` 3.5 版本的默认配置文件，未修改
* `rc.lua.myconfig` 基于 3.5 版本的个人配置文件
* `rc.lua.4.2` 4.2 新默认配置，未修改

思路为利用 `diff` 提取出个人配置与 3.5 默认配置文件的增量补丁，然后把补丁应用在 4.2 的文件上实现迁移。

### `制作补丁`

```
$ diff -urN rc.lua.3.5 rc.lua.myconfig  > mypatch.patch
```

### `应用补丁`

```
$ patch rc.lua.4.2 -i mypatch.patch -o rc.lua
patching file rc.lua (read from rc.lua.4.2)
Hunk #1 FAILED at 38.
Hunk #2 FAILED at 55.
Hunk #3 succeeded at 101 with fuzz 1 (offset 5 lines).
Hunk #4 succeeded at 276 with fuzz 2 (offset 29 lines).
2 out of 4 hunks FAILED -- saving rejects to file rc.lua.rej
```

显然应用没有完全成功，其中在 38 行以及 55 行应用失败，并记录在 `rc.lua.rej` 里。

```
$ cat rc.lua.rej 
--- rc.lua.3.5  2018-05-12 19:15:54.922286085 +0800
+++ rc.lua.myconfig 2018-05-12 19:13:35.057911463 +0800
@@ -38,10 +38,10 @@

 -- {{{ Variable definitions
 -- Themes define colours, icons, font and wallpapers.
-beautiful.init("@AWESOME_THEMES_PATH@/default/theme.lua")
+beautiful.init("~/.config/awesome/default/theme.lua")

 -- This is used later as the default terminal and editor to run.
-terminal = "xterm"
+terminal = "xfce4-terminal"
 editor = os.getenv("EDITOR") or "nano"
 editor_cmd = terminal .. " -e " .. editor

@@ -55,18 +55,18 @@
 -- Table of layouts to cover with awful.layout.inc, order matters.
 local layouts =
 {
-    awful.layout.suit.floating,
-    awful.layout.suit.tile,
-    awful.layout.suit.tile.left,
-    awful.layout.suit.tile.bottom,
-    awful.layout.suit.tile.top,
+--    awful.layout.suit.floating,
+--    awful.layout.suit.tile,
+--    awful.layout.suit.tile.left,
+--    awful.layout.suit.tile.bottom,
+--    awful.layout.suit.tile.top,
     awful.layout.suit.fair,
     awful.layout.suit.fair.horizontal,
     awful.layout.suit.spiral,
     awful.layout.suit.spiral.dwindle,
     awful.layout.suit.max,
     awful.layout.suit.max.fullscreen,
-    awful.layout.suit.magnifier
+--    awful.layout.suit.magnifier
 }
 -- }}}
```

这里是主题，终端，以及常用布局的个人设置。

### `修正补丁`

再次通过对比补丁文件与 4.2 文件，发现 38 行区块是要删除的东西不匹配，而 55 行区块则是上下文与要删除的内容均不匹配，导致不能应用补丁，于是手动修改补丁

```
$ vim mypatch.patch

--- rc.lua.3.5  2018-05-12 19:15:54.922286085 +0800
+++ rc.lua.myconfig 2018-05-12 19:13:35.057911463 +0800
@@ -38,10 +38,10 @@

 -- {{{ Variable definitions
 -- Themes define colours, icons, font and wallpapers.
-beautiful.init(gears.filesystem.get_themes_dir() .. "default/theme.lua")
+beautiful.init("~/.config/awesome/default/theme.lua")

 -- This is used later as the default terminal and editor to run.
-terminal = "xterm"
+terminal = "xfce4-terminal"
 editor = os.getenv("EDITOR") or "nano"
 editor_cmd = terminal .. " -e " .. editor

@@ -55,18 +55,18 @@

 -- Table of layouts to cover with awful.layout.inc, order matters.
 awful.layout.layouts = {
-    awful.layout.suit.floating,
-    awful.layout.suit.tile,
-    awful.layout.suit.tile.left,
-    awful.layout.suit.tile.bottom,
-    awful.layout.suit.tile.top,
+--    awful.layout.suit.floating,
+--    awful.layout.suit.tile,
+--    awful.layout.suit.tile.left,
+--    awful.layout.suit.tile.bottom,
+--    awful.layout.suit.tile.top,
     awful.layout.suit.fair,
     awful.layout.suit.fair.horizontal,
     awful.layout.suit.spiral,
     awful.layout.suit.spiral.dwindle,
     awful.layout.suit.max,
     awful.layout.suit.max.fullscreen,
-    awful.layout.suit.magnifier,
+--    awful.layout.suit.magnifier,
     awful.layout.suit.corner.nw,
     -- awful.layout.suit.corner.ne,
     -- awful.layout.suit.corner.sw,
....
....
```

输出省略显示，有兴趣的读者可以仔细与`rc.lua.rej` 文件对比，看看笔者是怎样改的。

### `再次应用补丁`

```
$ patch rc.lua.4.2 -i mypatch.patch -o rc.lua
patching file rc.lua (read from rc.lua.4.2)
Hunk #1 succeeded at 41 (offset 3 lines).
Hunk #2 succeeded at 57 with fuzz 2 (offset 2 lines).
Hunk #3 succeeded at 101 with fuzz 1 (offset 5 lines).
Hunk #4 succeeded at 276 with fuzz 2 (offset 29 lines).
$ cp rc.lua ~/.config/awesome/rc.lua  ### 打完补丁直接使用
```

### **总结**

`diff` 与 `patch` 配合使用，能当增量备份，而且还可以将补丁分发给他人使用，而且在日常的软件包打补丁也具有重要的意义，特别是内核补丁或者一些驱动补丁，打补丁遇到错误时候可以尝试自己修改，已满足自身特殊要求，修改的时候一定要抓住 2 个非常重要的要素：

1. 要修改的内容是否匹配？特别是要删除的
2. 上下文是否满足，特别是距离要修改的地方前后一行，以及上下文的行数是否满足，默认是 3 行上下文
