diff 与 patch:比较文件、目录和处理补丁
本文从 Shell 命令手册迁出,集中维护文件差异、补丁和比较脚本;基础入口见 Shell 命令手册。
# diff - 文件比较工具
比较文件或目录的差异,是版本控制和配置管理的重要工具
# 基本语法
diff [选项] 文件1 文件2
diff [选项] 目录1 目录2
2
# 常用选项速查
| 选项 | 说明 | 使用场景 |
|---|---|---|
-u | 统一格式输出(推荐) | 代码审查、生成补丁 |
-c | 上下文格式输出 | 需要更多上下文信息 |
-q | 只显示是否不同 | 快速检查文件是否相同 |
-s | 显示相同文件 | 目录比较时查看相同文件 |
-r | 递归比较目录 | 比较项目目录 |
-N | 将缺失文件视为空 | 新增/删除文件的对比 |
-i | 忽略大小写 | 不区分大小写的比较 |
-w | 忽略空白字符 | 忽略缩进和空格差异 |
-B | 忽略空行 | 忽略空行变化 |
# 快速上手
1. 基本文件比较
# 最简单的比较
diff file1.txt file2.txt
# 推荐:统一格式输出(最易读)
diff -u file1.txt file2.txt
# 快速检查:只显示是否不同
diff -q file1.txt file2.txt
2
3
4
5
6
7
8
输出示例:
--- file1.txt 2025-01-19 10:00:00
+++ file2.txt 2025-01-19 11:00:00
@@ -1,3 +1,4 @@
line 1 # 未改变
-old line 2 # 删除的行
+new line 2 # 新增的行
+extra line # 新增的行
line 3 # 未改变
2
3
4
5
6
7
8
输出符号说明:
| 符号 | 含义 |
|---|---|
- | 从 file1 删除的行 |
+ | 在 file2 新增的行 |
(空格) | 未改变的行 |
--- | file1 的文件名和时间 |
+++ | file2 的文件名和时间 |
@@ | 行号范围标识 |
# 实战场景
场景 1:配置文件管理
# 修改配置前备份
cp nginx.conf nginx.conf.bak
# 修改配置后比较差异
diff -u nginx.conf.bak nginx.conf
# 查看具体改了什么
diff -u nginx.conf.bak nginx.conf | less
# 确认无误后删除备份
rm nginx.conf.bak
# 如果改错了,快速恢复
cp nginx.conf.bak nginx.conf
2
3
4
5
6
7
8
9
10
11
12
13
14
场景 2:代码审查
# 比较两个版本的代码
diff -u old_version.py new_version.py
# 生成补丁文件供审查
diff -u old_version.py new_version.py > code_review.patch
# 查看补丁内容
cat code_review.patch
# 应用补丁
patch old_version.py < code_review.patch
2
3
4
5
6
7
8
9
10
11
场景 3:目录对比
# 递归比较两个项目目录
diff -r project_v1/ project_v2/
# 显示相同和不同的文件
diff -rs project_v1/ project_v2/
# 忽略特定文件类型
diff -r --exclude="*.log" --exclude="node_modules" \
old_project/ new_project/
2
3
4
5
6
7
8
9
场景 4:系统文件监控
# 备份重要系统文件
cp /etc/hosts /etc/hosts.original
# 定期检查是否被修改
diff -q /etc/hosts /etc/hosts.original
# 查看具体修改内容
diff -u /etc/hosts.original /etc/hosts
# 脚本自动监控
if diff -q /etc/hosts /etc/hosts.original > /dev/null; then
echo "hosts 未变化"
else
status=$?
case "$status" in
1) echo "警告:hosts 文件已被修改!"; diff -u /etc/hosts.original /etc/hosts ;;
*) printf '比较失败: %s\n' "$status" >&2; exit "$status" ;;
esac
fi
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
场景 5:数据文件比较
# 比较 CSV 文件(忽略空格)
diff -w data_v1.csv data_v2.csv
# 比较日志文件(忽略空行)
diff -B log_yesterday.txt log_today.txt
# 比较并保存差异
diff -u data_old.csv data_new.csv > data_changes.diff
2
3
4
5
6
7
8
# 忽略差异选项
# 忽略大小写
diff -i file1.txt file2.txt
# 忽略所有空白字符(空格、Tab)
diff -w file1.txt file2.txt
# 忽略行尾空白
diff -Z file1.txt file2.txt
# 忽略空行
diff -B file1.txt file2.txt
# 组合使用:忽略大小写和空白
diff -iw file1.txt file2.txt
# 忽略匹配特定模式的行
diff -I '^#' file1.txt file2.txt # 忽略注释行
diff -I '^$' file1.txt file2.txt # 忽略空行
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 输出格式对比
1. 默认格式(简单但不直观)
diff file1.txt file2.txt
# 输出:
# 2c2
# < old line
# ---
# > new line
2
3
4
5
6
2. 统一格式 -u(推荐,最常用)
diff -u file1.txt file2.txt
# 输出:
# --- file1.txt
# +++ file2.txt
# @@ -1,3 +1,3 @@
# line1
# -old line
# +new line
# line3
2
3
4
5
6
7
8
9
3. 上下文格式 -c(详细)
diff -c file1.txt file2.txt
# 输出:
# *** file1.txt
# --- file2.txt
# *************** 1,3 ****
# line1
# ! old line
# line3
# --- 1,3 ----
# line1
# ! new line
# line3
2
3
4
5
6
7
8
9
10
11
12
4. 并排格式 -y(直观对比)
diff -y file1.txt file2.txt
# 输出:
# line1 line1
# old line | new line
# line3 line3
2
3
4
5
# 补丁文件操作
生成补丁:
# 生成单文件补丁
diff -u original.py modified.py > changes.patch
# 生成目录补丁
diff -Naur old_project/ new_project/ > project.patch
# 带描述信息的补丁
# 说明放在真正的 ---/+++ 文件头之前,不自造空文件名补丁头。
cat > feature.patch << 'EOF'
修复:登录页面验证错误
详细说明:
1. 修复邮箱验证正则表达式
2. 添加密码强度检查
EOF
diff -u old.py new.py >> feature.patch
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
应用补丁:
# 查看补丁内容
cat changes.patch
# 预览补丁效果(不实际应用)
patch --dry-run < changes.patch
# 应用补丁
patch original.py < changes.patch
# 应用目录补丁
patch -p1 < project.patch
# 撤销补丁
patch -R original.py < changes.patch
2
3
4
5
6
7
8
9
10
11
12
13
14
# 实用脚本
配置文件监控脚本:
#!/bin/bash
# config_monitor.sh - 监控配置文件变化
CONFIG="/etc/nginx/nginx.conf"
BACKUP="${CONFIG}.backup"
# 检查备份是否存在
if [ ! -f "$BACKUP" ]; then
echo "创建初始备份..."
cp "$CONFIG" "$BACKUP" || exit 1
exit 0
fi
# diff 的 1 表示不同,2 表示执行错误。I/O 错误时立即停,不进入恢复。
if diff -q "$BACKUP" "$CONFIG" > /dev/null; then
echo "✅ 配置文件未变化"
else
status=$?
[ "$status" -eq 1 ] || { printf '比较失败: %s\n' "$status" >&2; exit "$status"; }
echo "⚠️ 配置文件已变化,差异详情:"
if diff -u "$BACKUP" "$CONFIG"; then :; else
status=$?
[ "$status" -eq 1 ] || exit "$status"
fi
read -r -p "接受新配置填 y;恢复备份填 r;其他输入不修改: " reply || exit 1
case "$reply" in
y|Y) cp "$CONFIG" "$BACKUP" || exit 1; echo "✅ 备份已更新" ;;
r|R) cp "$BACKUP" "$CONFIG" || exit 1; echo "↩️ 已恢复原配置" ;;
*) echo "未修改文件" ;;
esac
fi
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
批量文件对比脚本:
#!/bin/bash
# compare_dirs.sh - 比较两个目录并生成报告
if [ "$#" -ne 2 ]; then
echo "用法: $0 <目录1> <目录2>" >&2
exit 2
fi
DIR1="$1"
DIR2="$2"
REPORT=$(mktemp "./diff_report_$(date +%Y%m%d_%H%M%S).XXXXXX") || exit 1
SUMMARY=$(mktemp) || exit 1
DETAIL=$(mktemp) || { rm -f "$SUMMARY"; exit 1; }
trap 'rm -f "$SUMMARY" "$DETAIL"' EXIT
# 分别验证比较状态;不把读取失败当作差异报告成功。
if LC_ALL=C diff -rq "$DIR1" "$DIR2" > "$SUMMARY"; then :; else
status=$?
[ "$status" -eq 1 ] || exit "$status"
fi
if LC_ALL=C diff -ru "$DIR1" "$DIR2" > "$DETAIL"; then :; else
status=$?
[ "$status" -eq 1 ] || exit "$status"
fi
{
printf '====== 目录比较报告 ======\n时间: %s\n目录1: %s\n目录2: %s\n' "$(date)" "$DIR1" "$DIR2"
echo "=== 不同的文件 ==="
grep ' differ$' "$SUMMARY" || :
echo "=== 仅在目录1存在(含子目录) ==="
grep -F "Only in $DIR1" "$SUMMARY" || :
echo "=== 仅在目录2存在(含子目录) ==="
grep -F "Only in $DIR2" "$SUMMARY" || :
echo "=== 原始目录摘要(完整保留) ==="
cat "$SUMMARY"
echo "=== 详细差异 ==="
cat "$DETAIL"
} > "$REPORT" || exit 1
printf '报告已生成: %s\n' "$REPORT"
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# 性能优化技巧
# 只检查是否相同(最快)
diff -q file1 file2
# 大文件比较(限制输出)
diff -u large1.log large2.log | head -100
# 二进制文件比较
diff -a binary1.bin binary2.bin
# 并行比较多个文件
for f in *.txt; do
diff -q "$f" "backup/$f" &
done
wait
2
3
4
5
6
7
8
9
10
11
12
13
14
# 与其他工具配合
# 与 git 配合
git diff # 查看工作区改动
git diff --cached # 查看暂存区改动
git diff HEAD~1 HEAD # 比较两个提交
# 与 vimdiff 配合
vimdiff file1.txt file2.txt # 可视化对比
# 与 colordiff 配合(彩色输出)
diff -u file1.txt file2.txt | colordiff
# 与 awk 配合统计
diff -u old new | awk '/^@@/ {h=1; next} h && /^\+/ {n++} END {print n+0}' # 单文件新增行数,跳过 +++ 文件头
diff -u old new | awk '/^@@/ {h=1; next} h && /^-/ {n++} END {print n+0}' # 单文件删除行数,跳过 --- 文件头
2
3
4
5
6
7
8
9
10
11
12
13
14
# 常见问题
Q: 为什么推荐使用 -u 选项?
A: 统一格式 (-u) 输出最易读,广泛用于代码审查和生成补丁,是 Git 的默认格式。
Q: 如何比较二进制文件?
A: diff -a 只是强制把二进制输入按文本行处理,可能输出终端控制字符;按字节比较用 cmp,或先用 hexdump 转为可读文本。
cmp file1.bin file2.bin # 二进制比较
hexdump -C file1.bin > hex1.txt # 转换为十六进制对比
hexdump -C file2.bin > hex2.txt
diff hex1.txt hex2.txt
2
3
4
Q: 如何忽略多种类型的差异?
A: 可以组合使用多个选项:
diff -iwB file1 file2 # 同时忽略大小写、空白、空行
# 最佳实践
- ✅ 总是使用
-u选项:输出格式最清晰 - ✅ 重要操作前先备份:
cp file file.bak - ✅ 使用
-q快速检查:批量比较时提高效率 - ✅ 保存差异为补丁:便于审查和应用
- ✅ 结合版本控制:Git 已内置强大的 diff 功能
- ✅ 编写脚本自动化:定期监控重要文件变化
# 替代工具推荐
| 工具 | 特点 | 使用场景 |
|---|---|---|
vimdiff | 可视化对比 | 交互式编辑 |
colordiff | 彩色输出 | 终端可视化 |
git diff | 版本控制集成 | Git 仓库 |
meld | GUI 工具 | 图形化对比 |
Beyond Compare | 专业工具 | 商业项目 |
# 退出码、补丁与脚本边界
| 工具/情况 | 状态与处理 |
|---|---|
diff | 0 相同,1 存在差异,2 执行错误(如文件不存在、无读取权限) |
cmp | 0 相同,1 不同,大于 1 为错误;按工具实现核对 |
patch --dry-run | 先检查补丁是否可应用,成功后再执行真实 patch;备份、路径层级、工作目录一并核对 |
diff -u … > change.patch | 状态 1 通常表示正常生成差异;状态 2 的输出不能作为有效补丁继续使用 |
if ! diff ... 会把内容不同和读取失败混为一类;严格模式下应放在 if 中显式区分。上文交互监控只在比较确认为差异后提供接受/恢复选项;未确认及读取失败均停止修改。目录报告保留完整原始摘要,分类 grep 只为阅读便利,无法可靠解析任意换行/冒号文件名,不能用于后续自动删改。双重扫描也不是一致性快照,比较期间应避免并发修改。
macOS 自带 diff 的长选项/彩色输出取决于版本;--color、--ignore-trailing-space 等 GNU 扩展需核对本机 diff --help,不支持时用基础 diff -u 或明确安装 GNU diffutils。diff ... | head、统计管道还会涉及正常差异状态 1 和提前截断的 SIGPIPE,不能在严格模式下直接等同普通失败。
依据:GNU diff 退出状态 (opens new window)、GNU cmp (opens new window)、GNU patch (opens new window)。