Linux diff 与 patch:从文件对比到补丁应用与回退
本文要点
diff用于逐行比较两个文件或目录,输出哪一种行被修改、新增或删除,退出码 0/1/2 分别代表无差异、有差异、出错。- 普通格式适合人读,
diff -u统一格式行列号紧凑、上下文清晰,是补丁文件的标准格式,也是git diff的同款格式。 diff -i / -w / -B / -y分别忽略大小写、空白差异、空白行,或左右并排输出,适合对比不同版本的配置文件。diff -rq 目录1 目录2递归对比整个目录树,只列出有差异的文件名,是"部署前后对比"的利器。patch读取 diff 生成的补丁文件并应用到目标文件,patch -p0 --dry-run先模拟不落盘,patch -R一键回退。
开发或运维时,我们常遇到"两个文件到底差在哪"的问题:新版配置和线上配置相比改了什么?备份目录和当前目录多了哪些文件?别人给的改动怎么合进来?diff 和 patch 就是解决这类对比与合并问题的经典组合,也是 git 底层差异逻辑的来源。本文从最基础的对比讲起,一步步到生成补丁、应用补丁与回退。
diff 基础用法:输出差异与退出码
diff 的用法很直接:diff 文件1 文件2,把两个文件逐行比较并打印差异。比如 Linux 服务器上有两份 Nginx 风格的站点配置:
cat app.confserver {
listen 80;
server_name example.com;
root /var/www/example;
index index.html;
}app.conf.new 是后续要升级到的新版,改动了端口、增加了一行索引配置并开启了 access 日志。用 diff 一对比,差异一目了然:
diff app.conf app.conf.new
输出中的每一块都由三部分组成:
2c2:块头。2表示文件 1 的第 2 行,c是 change(修改),后面的2表示文件 2 的第 2 行。字母除了c还有a(append,新增)和d(delete,删除)。<开头:文件 1(app.conf)里的内容。>开头:文件 2(app.conf.new)里的内容。
所以 2c2 下方的 < listen 80; 和 > listen 8080; 意思是"第 2 行从 listen 80; 改成了 listen 8080;";5c5,6 表示第 5 行被替换成文件 2 的第 5、6 两行,即 index index.html; 换成了 index index.html index.php; 加上新增的 access_log off;。
除了打印差异,diff 还会返回退出码,脚本里常据此判断文件是否一致:
0:无差异(两文件完全相同)1:有差异2:出错(如文件不存在)
diff app.conf app.conf.new; echo "exit=$?"输出末尾的 echo 会显示 exit=1,证明两文件确实不同。
常用对比选项:忽略大小写、空白与并排
对比文件时,我们经常只想关心"实质差异",而忽略无伤大雅的空白、大小写或空行。diff 为此准备了一组开关:
| 选项 | 作用 | 适用场景 |
|---|---|---|
-i | 忽略大小写差异 | 对比受 Windows/Linux 换行风格差异影响的文本 |
-w | 忽略所有空白字符的差异 | 配置对齐后空格数变了 |
-B | 忽略空行的增删 | 排版时多加了空行 |
-y | 左右并排显示 | 想直观看到逐列对应关系 |
-W N | 配合 -y 指定并排宽度 | 并排输出按列对齐 |
实测:当 a.txt 是 Hello World、b.txt 是 hello world(仅大小写不同)时:
diff -i a.txt b.txt没有任何输出,同时 exit=0,说明 -i 成功忽略了大小写。同理,name=alice 与 name = alice(空格分布不同)在 -w 下也视为相同。
这些选项可以组合使用,比如 diff -iwB app.conf app.conf.new 同时忽略大小写、空白与空行。但要留意:忽略条件放得越宽,"相同"的判断就越宽松,适合快速筛选,真正上补丁时还是要用完整对比。
diff -u 统一格式:补丁文件的标准格式
上面普通格式按块编号输出,机器好理解,但人读起来不连贯。diff -u(unified,统一格式)把差异放在完整上下文里展示,行号用 -、+ 和 @@ 标注,这也是补丁文件(patch file)的事实标准:
diff -u app.conf app.conf.new
解读这份输出:
- 前两行
--- app.conf与+++ app.conf.new分别是被对比的两个文件及其修改时间。 @@ -1,6 +1,7 @@是差异的定位:-1,6指旧文件从第 1 行起共 6 行,+1,7指新文件从第 1 行起共 7 行,数字反映的是文件行数的变化。- 不带前缀的行是"上下文"(两边共有,未改动)。
-开头的是旧文件删掉的(listen 80;、index index.html;)。+开头的是新文件加入的(listen 8080;、index index.html index.php;、access_log off;)。
-u 格式把"改动最小化"体现得很清楚:只显示真正变化的行及其附近上下文,这正是它能成为补丁标准的原因。用过的读者会发现,这份输出和 git diff 的格式如出一辙——Git 就是借用 unified 格式来展示差异的。
目录对比:diff -r,找出两棵树差在哪
对比单个文件很常见,但在运维场景里,更常见的是对比整个目录树:备份目录和当前目录是否一致?发布新版本后部署目录和预期差了哪些文件?diff -r 递归对比目录下所有文件,重大对比时建议加上 -q,只列出"哪个文件不同",而不是把每个文件的内容差异都打出来:
diff -rq conf_a conf_b假设 conf_a 和 conf_b 各有一套站点配置,其中 server.conf 在新版本里做了修改,而 app.conf 完全一致,-rq 会简洁地输出:
Files conf_a/server.conf and conf_b/server.conf differ只有不一致的文件才会出现在结果里。若没有任何输出且退出码为 0,说明两个目录树逐字节一致——这正是"你的备份没问题"的强证据。实际应用中还常用到 diff -ru 生成目录级补丁,配合下文 patch 一次把多文件改动应用到另一台机器。
生成补丁:把 diff 输出存成文件
理解了 diff -u 后,生成补丁就是一步重定向:
diff -u app.conf app.conf.new > change.patch重定向不会在终端打印任何东西,但 change.patch 文件里已经保存了完整的 unified 差异。这个文件就是"补丁"。它的好处是:只含改动最小集,体积小、易传输,任何完好的目标文件只要打上这份补丁就能变成新版本,非常适合跨机器分发配置或代码改动。
patch 应用补丁:dry-run 模拟、应用与验证
patch 程序负责读取补丁文件并应用到目标文件。它默认会尝试从补丁里 ---/+++ 后面的文件名定位目标,所以一般配合 -p0(不剥离路径前缀)在"相对当前目录能匹配到该文件名"的位置执行。
动手改文件之前,强烈建议先用 --dry-run 模拟一遍。它只报告将要做什么(checking file),不会真正修改文件。模拟通过后再正式应用:
patch -p0 --dry-run < change.patch && patch -p0 < change.patch实测输出依次为:
checking file app.conf
patching file app.conf应用后用 diff 验证一下目标文件是否和预期的新版一致:
diff app.conf app.conf.new && echo IDENTICAL若 diff 无任何输出且跟随打印 IDENTICAL,说明 app.conf 已经被补丁改造成与 app.conf.new 完全相同的内容。整体流程截图:

回退补丁:patch -R 一键撤销
如果打上补丁后发现不合预期,patch -R 会反向应用补丁,把目标文件恢复到应用前的样子,相当于是补丁的"撤销":
patch -R -p0 < change.patch输出 patching file app.conf。再跑一遍 diff app.conf app.conf.new,差异又回来了,说明文件已还原。-R 之所以可行,是因为补丁文件里完整记录了每一行的增删,反向操作即可精确恢复。
小结:diff 与 patch 的日常分工
diff 负责"发现问题":单文件对比看细节、-u 看补丁格式、-rq 对比整个目录树;patch 负责"解决问题":--dry-run 先模拟、-p0 精确定位、-R 随时回退。两者配合,既能审计"改了什么",又能把改动安全地应用到目标环境。动手练习时,建议在测试目录里用自己改过的配置或代码反复演练,体会退出码、-u 行号与 -R 回退这几个关键点,很快就能上手。
评论 (0)
暂无评论,快来抢沙发吧!