# patch - 为开放源代码软件安装补丁程序

patch命令 被用于为开放源代码软件安装补丁程序。让用户利用设置修补文件的方式,修改,更新原始文件。如果一次仅修改一个文件,可直接在命令列中下达指令依序执行。如果配合修补文件的方式则能一次修补大批文件,这也是Linux系统核心的升级方法之一。

使用diff指令比较两个文件的差异,将差异结果保存到一个文件patchfile,patch指令可以将这个patchfile更新到原始文件中。这个命令的实际作用就是修补补丁,Linux内核用的就是这种更新方式。

patch程序接受包含由diff程序生成的差异列表的补丁文件,并将这些差异应用于一个或多个原始文件,生成修补版本。通常,修补版本被放在正本上。可以进行备份;请参阅-b或-备份选项。要修补的文件的名称通常取自修补程序文件,但如果只有一个要修补的文件,则可以在命令行中将其指定为原始文件。启动时,patch试图确定diff列表的类型,除非被“-c(--context)”,“-e (--ed)“,“-n (--normal)”,“-u (--unified)“选项所否决。Context diffs和normal diffs是由补丁程序本身应用的,而ed diffs只是通过管道提供给ed(1)编辑器。patch尝试跳过任何前导垃圾,应用差异,然后跳过任何跟踪垃圾。因此,您可以将包含diff列表的文章或消息提供给patch。

使用上下文差异,并在较小程度上与正常的差异,补丁可以检测什么时候,在补丁中提到的行号是不正确的,并试图找到正确的位置应用每一个补丁。作为第一猜测,它采用了前面提到的行号,加或减任何偏移量,用于应用以前的主存。如果这不是正确的位置,补丁会前后扫描一组与HORK中给出的上下文相匹配的行。第一个补丁程序寻找上下文中所有行匹配的位置。如果找不到这样的位置,并且是上下文差异,并且最大模糊因子被设置为1或更多,则会忽略第一行和最后一行上下文进行另一次扫描。如果失败,并且最大模糊因子设置为2或更多,则忽略前两行和最后两行上下文,并进行另一次扫描。(默认的最大Fuzz因子为2。)

前缀上下文少于后缀上下文的块(在应用Fuzz之后)必须在文件的开头应用,如果它们的第一行号是1。前缀上下文多于后缀上下文的块(在应用Fuzz之后)必须在文件末尾应用。

如果修补程序找不到安装该修补程序的位置,它会将该块放到一个拒绝文件中,该文件通常是输出文件的名称加上.rej后缀,或者#if.rej将生成一个过长的文件名(即使在单个字符#后面加上文件名),然后#替换文件名的最后一个字符)。被拒绝的块以统一的或上下文的diff格式出现。如果输入是一个正常的差异,那么许多上下文都是空的。拒绝文件中的块上的行号可能与修补程序文件中的行号不同:它们反映了大致位置补丁,认为失败的块属于新文件而不是旧文件。

当每个模块都完成时,您会被告知,如果这个块失败了,如果是的话,那么哪一行(在新的文件中)补丁认为应该继续执行。如果主程序安装在与diff中指定的行号不同的行,则会告诉您偏移量。一个单一的大偏移量可能表明一个大块头安装在错误的地方。你还会被告知,如果使用了一个模糊因素来进行匹配,那么你也应该稍微有点怀疑。如果给出了-详细的选项,你也会被告知与之完全匹配的大块头。

如果命令行中没有指定原始文件源文件,则修补程序试图使用以下规则从前面的垃圾中找出要编辑的文件的名称。

首先,patch获取候选文件名的有序列表,如下所示:

1)如果头是context diff,则patch在标题中使用旧的和新的文件名。如果名称没有足够的斜线来满足“-pnum”或“-strik=num”选项,则会忽略它。名称“/dev/null”也被忽略。

2)如果前面的垃圾中有Index:line,如果旧的和新的名称都不存在,或者如果修补程序符合POSIX,则修补程序在Index:line中使用名称。

3)为了下列规则的目的,候选人文件名被认为是按顺序(旧的、新的、索引的),而不管它们在头中出现的顺序是什么。

然后,修补程序从候选列表中选择一个文件名,如下所示

1)如果存在一些已命名的文件,则修补程序选择符合POSIX的名称,否则选择最佳名称。

2)如果patch没有忽略RCS、ClearCase、Perforce和SCCS(请参阅-g num或-get=num选项),并且除了找到RCS、ClearCase、Perforce或SCCS母版之外,不存在任何命名文件,则patch将选择具有RCS、ClearCase、Perforce或SCCS母版的第一个命名文件。

3)如果不存在命名文件,没有找到RCS、ClearCase、Perforce或SCCS母版,给出了一些名称,patch不符合POSIX,patch似乎创建了一个文件,选择了需要创建最少目录的最佳名称。

4)如果上面的启发式方法没有产生任何文件名,则会要求您提供要修补的文件的名称,而修补程序将选择该名称。

要确定非空文件名列表中的最佳名称,patch首先取所有具有最少路径名称组件的名称;然后,它以最短的basename获取所有名称;在这些名称中,它取所有最短的名称;最后,它取第一个剩余的名称。

所有这些的结果是,在新闻界面中,您应该能够说出如下所示

| patch -d /usr/src/local/blurfl

直接从包含修补程序的文章中修补blurfl目录中的文件。

如果修补程序文件包含多个修补程序,则patch试图应用每个修补程序,就好像它们来自不同的修补程序文件一样。这意味着,除其他外,假定要修补的文件的名称必须为每个diff列表确定,而每个diff列表之前的垃圾包含有趣的内容,如前面提到的文件名和修订级别。

# 适用范围

RedHat
RHEL
Ubuntu
CentOS
Debian
Deepin
SUSE
openSUSE
Fedora
Linux Mint
Alpine Linux
Arch Linux

# 语法

patch  [选项]  originfile  patchfile
patch  -pnum   <patchfile

# 选项

-b                           # 产生备份文件。在修补文件时,重命名或复制原始文件,而不是删除它。当备份不存在的文件时,将创建一个空的、不可读的备份文件作为占位符来表示不存在的文件。有关如何确定备份文件名的详细信息,请参阅-V或--version-control选项。
--backup-if-missmatch        # 在修补数据不完全吻合,而且没有可以要求备份文件的时候才备份。这是默认的,除非修补程序符合POSIX。
--no-backup-if-mismatch      # 如果修补程序与文件不完全匹配,如果不请求备份,则不要备份文件。这是默认的,如果补丁符合POSIX。
-B pref,  --prefix=pref      # 使用简单方法确定备份文件名(请参阅-V方法或--version-control方法选项),并在生成备份文件名时将pref追加到文件名。例如,使用“-B /junk/”的时候,“src/patch/util.c”的简单备份文件名是“/junk/src/patch/util.c”。
-binary                      # 以二进制模式写入所有文件,但标准输出和/dev/tty除外。读取时,禁用将CRLF线尾转换为LF行尾的启发式方法。
-c, --context                # 将修补程序文件解释为普通上下文差异。
-d dir ,  --directory=dir    # 设置工作目录
-D flag ,  --ifdef=define    # 用指定的标志“#ifdef…#endif”去标记修改过的地方
--dry-run                    # 打印应用修补程序的结果,而不实际更改任何文件。
-e, --ed                     # 将修补数据解释性ed命令可以识别的数据
-E                           # 如果修补后输出的文件是空白,那么删除文件。通常,此选项是不必要的,因为修补程序可以检查标头上的时间戳,以确定修补后是否应该存在文件。但是,如果输入不是上下文差异,或者补丁符合POSIX,则除非提供此选项,否则修补程序不会删除空修补文件。当修补程序删除文件时,它还尝试删除任何空的祖先目录。
-f, --force                  # 假设用户确切地知道他或她正在做什么,并且不问任何问题。跳过标题不说明要修补哪个文件的修补程序。即使修补程序文件版本有错误的,也要执行;并假设补丁程序即使看起来是正确的
-F num ,  --fuzz=num         # 设置最大fuzz因子。此选项仅适用于具有上下文的差异,并导致修补程序忽略多达那么多行在寻找安装的地方。请注意,一个较大的fuzz因子增加了一个错误补丁的几率。默认的fuzz因子是2,它可能不会设置为上下文差异(通常为3)中的上下文行数
-g num ,  --get=num          # 当文件处于RCS或SCCS控制下,且不存在或只读,并与默认版本匹配时,或当文件处于ClearCase或Perforce控制下且不存在时,此选项控制修补程序的操作。如果num为正,则修补程序从修订控制系统中获取(或签出)文件;如果为零,修补程序会忽略RCS、ClearCase、Perforce和SCCS而不获取该文件;如果为负数,修补程序将询问用户是否获取该文件。如果设置了PATCH_GET环境变量,则该选项的默认值为零;否则,默认值为零。
-i file ,  --input=patchfile # 读取指定的修补文件
-l, --ignore-whitespace      # 松散匹配模式,以防tabs或空格在文件中被咀嚼。修补程序文件中的一个或多个空白序列与原始文件中的任何序列匹配,以及行尾的空白序列都被忽略。正常字符必须仍然完全匹配。上下文的每一行仍必须与原始文件中的一行匹配。
-n, normal                   # 将修补数据解释成一般性的差异
-N, --forward                # 忽略似乎已反转或已应用的修补程序
-o outfile ,  --output=outfile # 设置输出文件。如果outfile是要修补的文件之一,请不要使用此选项。当outfile为-时,将输出发送到标准输出,并发送通常会转到标准输出到标准错误的任何消息
-pnum, --strip=num           # 设置要剥离的路径层数。例如,假设修补程序文件中的文件名是“/u/howard/src/blurfl/blurfl.c”,使用-p0则不会修改,使用-p1结果是“u/howard/src/blurfl/blurfl.c”,使用-p4结果是“blurfl/blurfl.c”
--posix                      # 更严格地遵守POSIX标准:当从diff头直观文件名时,从列表(旧的、新的、索引)获取第一个现有文件;不要删除修补后为空的文件;不要问是从RCS、ClearCase、Perforce还是SCCS获取文件;要求命令行中的文件前面有所有选项;当出现不匹配时,不要备份文件。
--quoting-style=word         # 使用样式字引用输出名称。这个词应该是下列之一:
                             # - literal,不改变输出
                             # - shell,如果shell包含shell元字符或将导致不明确的输出,则引用shell的名称。
                             # - shell-always,引用shell的名称,即使它们通常不需要引用。
                             # - c,引用C语言字符串的名称。
                             # - escape,除省略周围的双引号字符外,引用与c相同。
                             # - 你可以使用环境变量QUOTING_STYLE来设置这个选项的默认值,如果没有环境变量,那么默认shell。
-r rejectfile ,  --reject-file=rejectfile # 将拒绝放入拒绝文件,而不是默认的.rej文件。当拒绝文件为‘-’时,丢弃拒绝
-R, --reverse                # 假设此修补程序是用交换的旧文件和新文件创建的。修补程序尝试在应用之前交换每一个块。拒绝以交换格式出现。-R选项不适用于“ed diff”脚本,因为信息太少,无法重建反向操作。
--reject-format=format       # 以指定的格式(上下文或统一格式)生成拒绝文件。如果没有此选项,如果输入补丁是该格式的,则被拒绝的块以统一的diff格式出现,否则以普通的上下文diff形式出现。
-s, --silent, --quite        # 不显示执行过程,除非发生错误
-t, --batch                  # 自动跳过错误。
-u, --unified                # 将修补数据解释成一致化的差异
--verbose                    # 显示详细执行过程
-V method, --version-control=method # 确定备份文件名。method还可以由PATCH_VERSION_CONTROL、VERSION_CONTROL环境变量提供,环境变量被此选项覆盖。该方法不影响是否生成备份文件;它只影响已生成的任何备份文件的名称。method的有效值可以是:
                            # - existing,nil,对已经有编号的文件进行编号备份,否则进行简单备份。这是默认的
                            # - numbered,t,进行编号备份。例如F的编号备份文件名为F~N~其中N是版本号。
                            # - simple,never,做简单的备份。-B或--prefix、-Y或--basename-prefix、-z或--suffix选项指定简单的备份文件名。如果没有给出这些选项,则使用一个简单的备份后缀;如果设置了SIMPLE_BACKUP_SUFFIX环境变量的值,则为.orig。
-x num ,  --debug=num       # 只将感兴趣的内部调试标志设置为修补程序
-Y preg ,  --basename-prefix=pref # 使用简单方法确定备份文件名(请参阅-V),并在生成备份文件名时将pref前缀为文件名的basename。例如,使用“-Y .del/”选项,“src/patch/util.c”的简单备份文件名是“src/patch/.del/util.c”。
-z suffix ,  --suffix=suffix # 使用简单方法确定备份文件名(请参阅-V),并使用suffix作为后缀。例如,使用“-z -”选项,“src/patch/util.c”的备份文件名是“src/patch/util.c-”。
-Z, --set-utc               # 根据上下文diff标头中给出的时间戳设置修补文件的修改和访问时间,假设上下文diff标头使用协调的通用时间(UTC,通常称为GMT)。

--help                           # 显示帮助文档
--version                        # 显示命令版本信息

# 环境变量

  • PATCH_GET,这指定默认情况下修补程序是否会丢失rcs、ClearCase、Perforce或sccs中的只读文件;请参阅-g或-get选项。
  • POSIXLY_CORRECT,如果已设置,则默认情况下补丁程序更严格地符合POSIX标准:请参见-POSIX选项。
  • QUOTING_STYLE,--quoting-style选项的默认值。
  • SIMPLE_BACKUP_SUFFIX,扩展名用于简单备份文件名,而不是.orig
  • TMPDIR, TMP, TEM,放置临时文件的目录;修补程序使用此列表中设置的第一个环境变量。如果没有设置,则默认值依赖于系统;在unix主机上通常是/tmp。
  • VERSION_CONTROL or PATCH_VERSION_CONTROL,选择版本控制样式。

# patch发送者注意事项

如果你要发送补丁,有几件事你应该记住:

系统地创建补丁。一个很好的方法是命令“diff -Naur old new”。旧名和新名不应包含任何斜杠。diff命令的标题应该使用传统的Unix格式在通用时间中有日期和时间,这样补丁接收者就可以使用-Z或--set-utc选项。下面是一个示例命令,使用Bourneshell语法:“LC_ALL=C TZ=UTC0 diff -Naur gcc-2.7 gcc-2.8”。

告诉收件人如何应用修补程序,方法是告诉收件人要到哪个目录,以及要使用哪个修补程序选项。建议使用String-Np1选项。通过冒充收件人并将修补程序应用于原始文件的副本来测试您的过程。

您可以通过保存patchlevel.h文件来减轻人们的痛苦,该文件修补程序可以增加补丁级别,作为您发送的补丁文件中的第一个差异。如果您在补丁中放了一个prereq:line,它不会让它们在没有警告的情况下按顺序应用补丁。

您可以通过发送一个比较/dev/null的diff或一个日期为Epoch(1970-01-01:00:00:00 UTC)的空文件来创建一个文件。只有当要创建的文件在目标目录中不存在时,这才有效。相反,您可以通过发送上下文diff来删除文件,将要删除的文件与日期为Epoch的空文件进行比较。该文件将被删除,除非补丁符合POSIX和-E或-删除-空-文件选项没有提供。生成创建和删除文件的修补程序的一种简单方法是使用GNU diff的-N或-new-file选项。

如果收件人应该使用-pn选项,则不要发送如下所示的输出:

diff -Naur v2.0.29/prog/README prog/README
--- v2.0.29/prog/README   Mon Mar 10 15:13:12 1997
+++ prog/README   Mon Mar 17 14:58:22 1997

因为这两个文件名有不同的斜杠数,不同版本的修补程序对文件名的解释也不同。为了避免混淆,请发送如下所示的输出:

diff -Naur v2.0.29/prog/README v2.0.30/prog/README
--- v2.0.29/prog/README   Mon Mar 10 15:13:12 1997
+++ v2.0.30/prog/README   Mon Mar 17 14:58:22 1997

避免发送比较备份文件名(如README.orig)的修补程序,因为这可能会将补丁混淆为修补备份文件而不是真正的文件。相反,发送比较不同目录中相同的基本文件名的修补程序,例如,old/README和New/README

注意不要发出反向补丁,因为它使人们怀疑他们是否已经应用了补丁。

尽量不要让修补程序修改派生文件(例如,您的Makefile文件配置中有一行configure: configure.in),因为接收方无论如何都应该能够重新生成派生文件。如果必须发送派生文件的差异,则使用UTC生成差异,让接收方使用-z或-set-utc选项应用修补程序,并让它们删除任何依赖于修补文件的未修补文件(例如,使用make干净)。

虽然您可以通过将582个不同的列表放到一个文件中而不受影响,但最好将相关的补丁分组到单独的文件中,以防出现混乱。

# 警告

上下文差异不能可靠地表示空文件、空目录或符号链接等特殊文件的创建或删除。它们也不能表示对文件元数据的更改,如所有权、权限或一个文件是否是另一个文件的硬链接。如果这样的更改也是必需的,那么单独的指令(例如shell脚本)应该伴随补丁。

patch无法判断在ed脚本中行号是否关闭,只有在发现更改或删除时,才能在普通差异中检测到不良行号。使用模糊因子3的上下文差异可能存在同样的问题。在这些情况下,您可能应该做一个上下文差异,看看这些更改是否有意义。当然,没有错误的编译是一个很好的指示,表明补丁可以工作,但并不总是这样。

patch通常会产生正确的结果,即使需要进行大量的猜测。但是,只有当修补程序应用于与修补程序生成的文件完全相同的版本时,才能保证结果是正确的。

# 举例

[sogrey@bogon demos]$ diff test.txt test2.txt > diff.txt
[sogrey@bogon demos]$ cat diff.txt
1,2d0
< 石家庄今日新增16例确诊病例
< 中国留美博士遇害 美驻华使馆慰问
4,6c2,3
< 理塘文旅公司回应丁真抽烟
< 北京一确诊者隐瞒行程不配合流调
< 山西晋中新增2例无症状感染者
---
> 
> 1月11日,美国第一夫人梅拉尼娅·特朗普通过白宫发表声明,谴责上周发生在美国国会的暴乱。
[sogrey@bogon demos]$ patch -p0 test.txt diff.txt
patching file test.txt
[sogrey@bogon demos]$ cat test.txt
特朗普夫人发文谴责国会暴乱

1月11日,美国第一夫人梅拉尼娅·特朗普通过白宫发表声明,谴责上周发生在美国国会的暴乱。
[sogrey@bogon demos]$