patch 是 Linux 中用于 应用补丁文件到源代码或文本文件 的命令,它读取 diff 命令生成的差异文件,并将这些修改应用到原始文件。
📦 基本语法
bash
patch [选项] [原始文件 [补丁文件]]
# 常用形式:patch -p数字 < 补丁文件
🎯 主要功能
- 应用补丁:将 diff 生成的补丁应用到原始文件
- 撤销补丁:还原已应用的补丁修改
- 版本管理:在软件开发和维护中管理代码变更
- 批量修改:一次性对多个文件应用修改
💡 常用选项
| 选项 | 说明 | 示例 |
|---|---|---|
-pN |
剥离路径前缀的层数(N为数字) | patch -p1 < patchfile |
-i |
指定补丁文件 | patch -i changes.patch |
-R |
反向应用(撤销补丁) | patch -R < patchfile |
-N |
忽略已应用的补丁 | patch -N < patchfile |
-E |
删除应用后为空文件的文件 | patch -E < patchfile |
-b |
创建备份文件(.orig) | patch -b < patchfile |
--dry-run |
试运行,不实际修改文件 | patch --dry-run < patchfile |
-u |
统一格式补丁(默认) | patch -u < patchfile |
-c |
上下文格式补丁 | patch -c < patchfile |
-d |
切换到指定目录再打补丁 | patch -d /path/to/src < patchfile |
-F |
设置模糊匹配行数 | patch -F 3 < patchfile |
-l |
忽略空白差异 | patch -l < patchfile |
-s |
静默模式,不显示提示信息 | patch -s < patchfile |
-v |
显示版本信息 | patch -v |
-h |
显示帮助信息 | patch -h |
🔧 实际应用示例
示例 1:基本补丁应用
bash
# 1. 创建原始文件和修改后的文件
echo "Hello World" > original.txt
echo "Hello Linux World" > modified.txt
# 2. 生成补丁文件
diff -u original.txt modified.txt > hello.patch
cat hello.patch
# 输出:
# --- original.txt 2024-01-01 10:00:00
# +++ modified.txt 2024-01-01 10:00:01
# @@ -1 +1 @@
# -Hello World
# +Hello Linux World
# 3. 应用补丁
patch original.txt < hello.patch
# 或
patch -i hello.patch original.txt
# 4. 验证修改
cat original.txt
# 输出:Hello Linux World
# 5. 撤销补丁
patch -R original.txt < hello.patch
cat original.txt
# 输出:Hello World(恢复原状)
示例 2:目录级别的补丁
bash
# 创建测试目录结构
mkdir -p project/{src,include}
echo "int main() { return 0; }" > project/src/main.c
echo "void hello();" > project/include/hello.h
# 复制并修改
cp -r project project_modified
echo "// 添加注释" >> project_modified/src/main.c
echo "#include <stdio.h>" >> project_modified/include/hello.h
# 生成递归差异补丁
diff -ru project project_modified > project.patch
# 查看补丁内容
head -20 project.patch
# 输出:
# diff -ru project/include/hello.h project_modified/include/hello.h
# --- project/include/hello.h
# +++ project_modified/include/hello.h
# @@ -1 +1,2 @@
# void hello();
# +#include <stdio.h>
# diff -ru project/src/main.c project_modified/src/main.c
# --- project/src/main.c
# +++ project/src/main.c
# @@ -1 +1,2 @@
# int main() { return 0; }
# +// 添加注释
# 应用补丁到原始项目
cd project
patch -p1 < ../project.patch
# -p1 表示剥离第一层路径前缀(去掉 "project/")
# 验证修改
cat src/main.c
cat include/hello.h
示例 3:软件源码补丁应用
bash
# 假设下载了软件源码和补丁文件
# 1. 解压源码
tar -xzf software-1.0.tar.gz
cd software-1.0
# 2. 查看补丁文件头信息
head -10 ../security-fix.patch
# 通常包含类似内容:
# --- software-1.0/src/file.c
# +++ software-1.0-fixed/src/file.c
# 3. 试运行(不实际修改)
patch -p1 --dry-run < ../security-fix.patch
# 4. 创建备份并应用补丁
patch -p1 -b < ../security-fix.patch
# -b 会创建 .orig 备份文件
# 5. 检查应用结果
find . -name "*.orig" # 查看备份文件
find . -name "*.rej" # 查看拒绝文件(如果有冲突)
# 6. 如果补丁失败,查看拒绝文件
if [ -f *.rej ]; then
echo "补丁应用有冲突,请查看 .rej 文件"
cat *.rej
fi
# 7. 编译测试
./configure
make
make test
# 8. 如果需要撤销补丁
patch -p1 -R < ../security-fix.patch
示例 4:处理补丁冲突
bash
# 创建有冲突的场景
echo "Line 1\nLine 2\nLine 3" > file.txt
echo "Line 1\nLine 2 modified\nLine 3" > file_v1.txt
echo "Line 1\nLine 2 changed\nLine 3" > file_v2.txt
# 生成两个不同的补丁
diff -u file.txt file_v1.txt > patch1.patch
diff -u file.txt file_v2.txt > patch2.patch
# 应用第一个补丁
patch file.txt < patch1.patch
cat file.txt
# 输出:
# Line 1
# Line 2 modified
# Line 3
# 尝试应用第二个补丁(会有冲突)
patch file.txt < patch2.patch
# 输出:
# patching file file.txt
# Hunk #1 FAILED at 1.
# 1 out of 1 hunk FAILED -- saving rejects to file file.txt.rej
# 查看拒绝文件
cat file.txt.rej
# 输出:
# --- file.txt
# +++ file.txt
# @@ -1,3 +1,3 @@
# Line 1
# -Line 2
# +Line 2 changed
# Line 3
# 手动解决冲突
echo "需要手动编辑 file.txt 解决冲突"
echo "当前内容:"
cat file.txt
echo ""
echo "期望修改:"
cat file.txt.rej
# 手动编辑后,可以继续应用剩余的补丁
patch --force < patch2.patch
示例 5:高级补丁管理
bash
#!/bin/bash
# 补丁管理脚本
PATCH_DIR="/opt/patches"
SOURCE_DIR="/usr/src/software"
BACKUP_DIR="/var/backup/patches"
# 1. 应用所有补丁
apply_all_patches() {
echo "正在应用所有补丁..."
# 按数字顺序应用补丁
for patch_file in $(ls $PATCH_DIR/*.patch | sort -V); do
echo "应用补丁: $(basename $patch_file)"
# 检查是否已应用
if patch -p1 --dry-run -s < "$patch_file" 2>/dev/null; then
echo " 补丁已应用或无需应用"
continue
fi
# 应用补丁并创建备份
if ! patch -p1 -b -E -i "$patch_file"; then
echo " 应用失败: $patch_file"
echo " 请检查 $SOURCE_DIR 中的 .rej 文件"
return 1
fi
# 记录已应用的补丁
echo "$(date): $(basename $patch_file)" >> "$BACKUP_DIR/applied_patches.log"
# 移动补丁文件到已应用目录
mv "$patch_file" "$BACKUP_DIR/applied/"
done
echo "所有补丁应用完成"
}
# 2. 撤销特定补丁
revert_patch() {
local patch_name="$1"
local patch_file="$BACKUP_DIR/applied/$patch_name"
if [ ! -f "$patch_file" ]; then
echo "错误: 补丁文件不存在: $patch_name"
return 1
fi
echo "正在撤销补丁: $patch_name"
# 反向应用补丁
if patch -p1 -R -i "$patch_file"; then
echo "补丁撤销成功"
# 移动回补丁目录
mv "$patch_file" "$PATCH_DIR/"
# 更新日志
sed -i "/$patch_name/d" "$BACKUP_DIR/applied_patches.log"
else
echo "补丁撤销失败"
return 1
fi
}
# 3. 验证补丁状态
check_patch_status() {
echo "=== 补丁状态检查 ==="
echo ""
# 检查已应用的补丁
echo "已应用的补丁:"
if [ -f "$BACKUP_DIR/applied_patches.log" ]; then
cat "$BACKUP_DIR/applied_patches.log"
else
echo "无记录"
fi
echo ""
# 检查待应用的补丁
echo "待应用的补丁:"
ls $PATCH_DIR/*.patch 2>/dev/null | while read patch; do
patch_name=$(basename "$patch")
if patch -p1 --dry-run -s < "$patch" 2>/dev/null; then
echo " $patch_name: 已应用"
else
echo " $patch_name: 未应用"
fi
done
echo ""
# 检查拒绝文件
echo "补丁冲突文件:"
find $SOURCE_DIR -name "*.rej" 2>/dev/null | while read rej_file; do
echo " $rej_file"
done
}
# 4. 创建新补丁
create_patch() {
local original_dir="$1"
local modified_dir="$2"
local patch_name="$3"
if [ -z "$patch_name" ]; then
patch_name="$(date +%Y%m%d_%H%M%S).patch"
fi
echo "正在创建补丁: $patch_name"
# 生成补丁
diff -Nru "$original_dir" "$modified_dir" > "$PATCH_DIR/$patch_name"
if [ $? -eq 0 ]; then
echo "补丁创建成功: $PATCH_DIR/$patch_name"
echo "补丁内容预览:"
head -20 "$PATCH_DIR/$patch_name"
else
echo "补丁创建失败"
return 1
fi
}
# 主菜单
main_menu() {
echo "补丁管理系统"
echo "1. 应用所有补丁"
echo "2. 撤销补丁"
echo "3. 检查补丁状态"
echo "4. 创建新补丁"
echo "5. 退出"
read -p "请选择操作: " choice
case $choice in
1)
cd "$SOURCE_DIR" && apply_all_patches
;;
2)
read -p "请输入要撤销的补丁文件名: " patch_name
cd "$SOURCE_DIR" && revert_patch "$patch_name"
;;
3)
check_patch_status
;;
4)
read -p "请输入原始目录: " orig_dir
read -p "请输入修改后目录: " mod_dir
read -p "请输入补丁文件名(可选): " patch_name
create_patch "$orig_dir" "$mod_dir" "$patch_name"
;;
5)
exit 0
;;
*)
echo "无效选择"
;;
esac
}
# 创建目录
mkdir -p "$PATCH_DIR" "$BACKUP_DIR/applied"
# 运行主菜单
while true; do
main_menu
echo ""
read -p "按回车键继续..."
clear
done
示例 6:内核补丁应用
bash
#!/bin/bash
# Linux内核补丁应用示例
KERNEL_VERSION="6.1.0"
KERNEL_SOURCE="/usr/src/linux-$KERNEL_VERSION"
PATCH_FILE="/tmp/kernel-patch.bz2"
echo "=== Linux内核补丁应用 ==="
echo "内核版本: $KERNEL_VERSION"
echo "源码目录: $KERNEL_SOURCE"
echo ""
# 1. 准备内核源码
if [ ! -d "$KERNEL_SOURCE" ]; then
echo "下载内核源码..."
wget "https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-$KERNEL_VERSION.tar.xz"
tar -xf "linux-$KERNEL_VERSION.tar.xz" -C /usr/src/
fi
cd "$KERNEL_SOURCE"
# 2. 清理源码树
echo "清理源码树..."
make mrproper
# 3. 应用补丁(假设补丁已下载)
if [ -f "$PATCH_FILE" ]; then
echo "应用内核补丁..."
# 解压并应用补丁
bzip2 -dc "$PATCH_FILE" | patch -p1 --verbose
# 检查应用结果
if [ $? -eq 0 ]; then
echo "补丁应用成功"
# 检查是否有拒绝文件
reject_files=$(find . -name "*.rej" | wc -l)
if [ $reject_files -gt 0 ]; then
echo "警告: 发现 $reject_files 个拒绝文件"
find . -name "*.rej" -exec echo " {}" \;
fi
else
echo "补丁应用失败"
exit 1
fi
else
echo "未找到补丁文件: $PATCH_FILE"
echo "跳过补丁应用步骤"
fi
# 4. 配置内核
echo "配置内核..."
make menuconfig
# 5. 编译内核
echo "开始编译内核..."
make -j$(nproc)
# 6. 安装内核模块
echo "安装内核模块..."
sudo make modules_install
# 7. 安装内核
echo "安装内核..."
sudo make install
# 8. 更新引导配置
echo "更新引导配置..."
if [ -f /etc/default/grub ]; then
sudo update-grub
elif [ -f /boot/grub2/grub.cfg ]; then
sudo grub2-mkconfig -o /boot/grub2/grub.cfg
fi
echo "内核补丁应用完成,请重启系统"
示例 7:Git补丁工作流
bash
#!/bin/bash
# Git补丁工作流示例
echo "=== Git补丁工作流 ==="
echo ""
# 1. 从Git生成补丁
echo "1. 从Git提交生成补丁"
git log --oneline -3 # 查看最近提交
read -p "输入要生成补丁的提交哈希: " commit_hash
# 生成单个提交的补丁
git format-patch -1 "$commit_hash" --stdout > single.patch
echo "单个提交补丁已生成: single.patch"
# 生成多个提交的补丁
git format-patch -3 # 最近3个提交
echo "多个提交补丁已生成"
# 2. 应用Git补丁
echo ""
echo "2. 应用Git补丁"
echo "应用补丁前状态:"
git status
# 检查补丁
git apply --check single.patch
if [ $? -eq 0 ]; then
echo "补丁可以应用"
# 试应用
git apply --stat single.patch
# 实际应用
git apply single.patch
# 或使用 git am 保留提交信息
# git am single.patch
else
echo "补丁有冲突,无法应用"
git apply --reject single.patch
echo "请手动解决冲突,然后运行: git add . && git am --continue"
fi
# 3. 创建Git补丁集
echo ""
echo "3. 创建补丁集发送邮件"
# 生成补丁集
git format-patch --cover-letter -3 --thread=shallow origin/main
# 发送补丁(需要配置git send-email)
# git send-email --to=linux-kernel@vger.kernel.org *.patch
# 4. 从邮件应用补丁
echo ""
echo "4. 从邮件应用补丁"
# 保存邮件到文件
# cat email.patch | git am
# 或从mbox文件应用
# git am /path/to/mbox
# 5. 补丁管理
echo ""
echo "5. 补丁管理"
# 查看已应用的补丁
git log --oneline --grep="patch" --grep="fix" --grep="bug"
# 创建补丁分支
git checkout -b patch-test
git am *.patch
# 测试补丁
make test
# 如果测试通过,合并到主分支
git checkout main
git merge patch-test
# 清理
git branch -d patch-test
rm *.patch
echo "Git补丁工作流完成"
示例 8:自动化补丁测试
bash
#!/bin/bash
# 自动化补丁测试系统
PATCH_FILE="$1"
TEST_DIR="/tmp/patch_test_$(date +%s)"
ORIGINAL_SOURCE="/usr/src/project"
LOG_FILE="/var/log/patch_test.log"
# 创建测试环境
setup_test_environment() {
echo "设置测试环境..."
mkdir -p "$TEST_DIR"
cp -r "$ORIGINAL_SOURCE" "$TEST_DIR/original"
cp -r "$ORIGINAL_SOURCE" "$TEST_DIR/patched"
# 应用补丁
cd "$TEST_DIR/patched"
if ! patch -p1 < "$PATCH_FILE" 2>&1 | tee "$TEST_DIR/patch_apply.log"; then
echo "补丁应用失败" | tee -a "$LOG_FILE"
return 1
fi
return 0
}
# 运行测试套件
run_tests() {
echo "运行测试套件..."
# 原始版本测试
echo "=== 原始版本测试 ==="
cd "$TEST_DIR/original"
if [ -f "Makefile" ]; then
make clean
make 2>&1 | tee "$TEST_DIR/build_original.log"
if [ $? -ne 0 ]; then
echo "原始版本编译失败" | tee -a "$LOG_FILE"
return 1
fi
# 运行测试
if [ -f "test.sh" ]; then
./test.sh 2>&1 | tee "$TEST_DIR/test_original.log"
fi
fi
# 补丁版本测试
echo "=== 补丁版本测试 ==="
cd "$TEST_DIR/patched"
if [ -f "Makefile" ]; then
make clean
make 2>&1 | tee "$TEST_DIR/build_patched.log"
if [ $? -ne 0 ]; then
echo "补丁版本编译失败" | tee -a "$LOG_FILE"
return 1
fi
# 运行测试
if [ -f "test.sh" ]; then
./test.sh 2>&1 | tee "$TEST_DIR/test_patched.log"
fi
fi
return 0
}
# 比较测试结果
compare_results() {
echo "比较测试结果..."
# 比较编译输出
if ! diff "$TEST_DIR/build_original.log" "$TEST_DIR/build_patched.log" > "$TEST_DIR/build_diff.log"; then
echo "编译输出有差异" | tee -a "$LOG_FILE"
echo "差异内容:"
cat "$TEST_DIR/build_diff.log"
fi
# 比较测试输出
if [ -f "$TEST_DIR/test_original.log" ] && [ -f "$TEST_DIR/test_patched.log" ]; then
if ! diff "$TEST_DIR/test_original.log" "$TEST_DIR/test_patched.log" > "$TEST_DIR/test_diff.log"; then
echo "测试输出有差异" | tee -a "$LOG_FILE"
echo "差异内容:"
cat "$TEST_DIR/test_diff.log"
fi
fi
# 检查性能差异
if [ -f "$TEST_DIR/original/benchmark" ] && [ -f "$TEST_DIR/patched/benchmark" ]; then
echo "性能测试..."
time "$TEST_DIR/original/benchmark" > "$TEST_DIR/bench_original.log" 2>&1
time "$TEST_DIR/patched/benchmark" > "$TEST_DIR/bench_patched.log" 2>&1
fi
}
# 生成测试报告
generate_report() {
echo "生成测试报告..."
REPORT_FILE="$TEST_DIR/patch_test_report_$(date +%Y%m%d_%H%M%S).txt"
cat > "$REPORT_FILE" << EOF
补丁测试报告
============
测试时间: $(date)
补丁文件: $(basename "$PATCH_FILE")
测试目录: $TEST_DIR
1. 补丁应用状态
$(cat "$TEST_DIR/patch_apply.log")
2. 编译结果
原始版本: $(grep -c "error" "$TEST_DIR/build_original.log" || echo 0) 个错误
补丁版本: $(grep -c "error" "$TEST_DIR/build_patched.log" || echo 0) 个错误
3. 测试结果
$(if [ -f "$TEST_DIR/test_original.log" ]; then
echo "原始版本测试:"
tail -5 "$TEST_DIR/test_original.log"
fi)
$(if [ -f "$TEST_DIR/test_patched.log" ]; then
echo "补丁版本测试:"
tail -5 "$TEST_DIR/test_patched.log"
fi)
4. 差异分析
$(if [ -f "$TEST_DIR/build_diff.log" ] && [ -s "$TEST_DIR/build_diff.log" ]; then
echo "编译差异:"
head -20 "$TEST_DIR/build_diff.log"
fi)
$(if [ -f "$TEST_DIR/test_diff.log" ] && [ -s "$TEST_DIR/test_diff.log" ]; then
echo "测试差异:"
head -20 "$TEST_DIR/test_diff.log"
fi)
5. 结论
$(if [ $? -eq 0 ]; then
echo "✅ 补丁测试通过"
else
echo "❌ 补丁测试失败"
fi)
EOF
echo "测试报告已生成: $REPORT_FILE"
cat "$REPORT_FILE"
# 记录到日志
cat "$REPORT_FILE" >> "$LOG_FILE"
}
# 清理测试环境
cleanup() {
echo "清理测试环境..."
rm -rf "$TEST_DIR"
}
# 主函数
main() {
if [ -z "$PATCH_FILE" ] || [ ! -f "$PATCH_FILE" ]; then
echo "用法: $0 <补丁文件>"
exit 1
fi
echo "开始补丁测试: $PATCH_FILE" | tee -a "$LOG_FILE"
echo "测试时间: $(date)" | tee -a "$LOG_FILE"
if ! setup_test_environment; then
echo "测试环境设置失败" | tee -a "$LOG_FILE"
cleanup
exit 1
fi
if ! run_tests; then
echo "测试运行失败" | tee -a "$LOG_FILE"
cleanup
exit 1
fi
compare_results
generate_report
cleanup
echo "补丁测试完成" | tee -a "$LOG_FILE"
}
# 执行主函数
main "$@"
📊 常见问题解决
问题 1:补丁应用失败(Hunk FAILED)
bash
# 1. 查看拒绝文件
cat *.rej
# 2. 手动应用补丁
patch -p1 --merge < patchfile
# 或使用交互模式
patch -p1 -i patchfile --merge
# 3. 增加模糊匹配行数
patch -p1 -F 10 < patchfile # 增加模糊匹配到10行
# 4. 忽略空白差异
patch -p1 -l < patchfile
# 5. 强制应用(可能损坏文件)
patch -p1 --force < patchfile
问题 2:路径前缀问题
bash
# 补丁文件中的路径:a/b/c/file.c
# 当前目录结构:b/c/file.c
# 需要剥离一层前缀
patch -p1 < patchfile # 去掉 "a/"
# 如果补丁路径是:../../src/file.c
# 当前在 src/ 目录
patch -p2 < patchfile # 去掉 "../../"
问题 3:补丁已部分应用
bash
# 检查补丁是否已应用
patch -p1 --dry-run -s < patchfile
# 如果已应用,跳过
patch -p1 -N < patchfile # 忽略已应用的补丁
# 或强制重新应用
patch -p1 -f < patchfile
问题 4:二进制文件补丁
bash
# 对于二进制文件,使用专门的工具
# 1. 使用 bsdiff/bspatch
bsdiff old.bin new.bin patch.bin
bspatch old.bin patched.bin patch.bin
# 2. 使用 xdelta
xdelta delta old.bin new.bin patch.xd
xdelta patch patch.xd old.bin patched.bin
# 3. 使用 git 二进制补丁
git diff --binary > patchfile
git apply patchfile
问题 5:补丁顺序问题
bash
# 按正确顺序应用补丁
for patch in $(ls patch-*.patch | sort -V); do
echo "应用补丁: $patch"
patch -p1 < "$patch" || break
done
# 或使用 quilt 工具管理补丁序列
quilt push -a # 应用所有补丁
quilt pop -a # 撤销所有补丁
quilt series # 查看补丁序列
💡 使用技巧
-
预览补丁:应用前先预览
bashpatch -p1 --dry-run --verbose < patchfile -
备份原始文件:总是创建备份
bashpatch -p1 -b -V numbered < patchfile # 创建 file.c.~1~ 格式的备份 -
批量应用补丁:使用循环
bashfor p in *.patch; do patch -p1 < "$p"; done -
生成反向补丁:从修改生成还原补丁
bashdiff -ru new old > revert.patch patch -p1 -R < revert.patch -
补丁验证:验证补丁完整性
bashpatch -p1 --dry-run --check < patchfile sha256sum original_file patched_file
⚠️ 注意事项
- 权限问题:确保对目标文件有写权限
- 编码问题:补丁文件和源文件编码要一致
- 行尾符:Windows(CRLF)和Linux(LF)行尾符差异
- 备份重要文件:应用补丁前备份重要文件
- 测试环境:先在测试环境验证补丁
- 版本匹配:确保补丁与软件版本匹配
- 依赖关系:注意补丁之间的依赖关系
🔄 典型工作流程
- 获取补丁:下载或生成补丁文件
- 验证补丁:检查补丁内容和格式
- 备份文件:备份要修改的文件
- 试运行 :
patch --dry-run测试 - 应用补丁:正式应用补丁
- 解决冲突 :处理
.rej拒绝文件 - 测试验证:编译和测试修改
- 记录变更:记录补丁应用情况
patch 命令是软件开发和系统维护中的重要工具,掌握它可以高效地管理代码变更和系统更新。