简介
本文的目的是向Linux新手介绍一种无价的资源,Larry Wall的patch程序。patch是用来查找文件之间差异的GNU
diff命令的一个接口;diff有很多选项,但是该命令最常用的用途是用来生成一个文件,该文件中列出了内容发生改变的行,显示两个原始文件、修改过的
行以及由于内容没有变化而忽略掉的行。patch典型地用于把一个目录下的源代码文件更新到新的版本,从而就避免了下载整个新的源代码档案的必要。下载一
个有效的patch仅仅需要下载发生变化的那些代码行就可以了。
patch最初源自十年前,那时网络带宽的限制促进了patch的发展,然而和当时的很多Unix工具一样,直到现在,patch还在广泛应用。在Dr.
Dobb之旅的2月份的程序员杂志中,Larry Wall对早期的patch做了一些很有趣的说明:
DDJ:顺便问一下,patch和diff哪个出现的早?
LW:从很长一段时间来说,diff出现地比较早。我想diff大约比patch早10年出现,一回想起来,我就纳闷为什么没有人早些想到使用patch
呢?
但是我想我知道这中间的原因。这很大程度上是心理因素使然。当开发出diff时,程序员增加了一个e选项,我想就是这个选项的原因,该选项后来滋生为一个
ed脚本,因此大家都会对自己说,"嗯,如果我想自动使用diff,那么我就使用这个选项。"因此从来都没有人编写一个计算机程序来获取其它格式的输出并
使用这些结果。或者是那些设计diff的人员,或者是那些使用diff格式而受益的人员太沉迷其中了,因为你可以对那些已经修改过的内容使用diff操作
并让这些内容正常工作都是很容易的。
diff和patch是一对工具,在数学上来说,diff是对两个集合的差运算,patch是对两个集合的和运算。
diff比较两个文件或文件集合的差异,并记录下来,生成一个diff文件,这也是我们常说的patch文件,即补丁文件。
patch能将diff文件运用于 原来的两个集合之一,从而得到另一个集合。举个例子来说文件A和文件B,经过diff之后生成了补丁文件C,那么着个过程相当于 A -B = C ,那么patch的过程就是B+C = A 或A-C =B。
因此我们只要能得到A, B, C三个文件中的任何两个,就能用diff和patch这对工具生成另外一个文件。
这就是diff和patch的妙处。下面分别介绍一下两个工具的用法:
1. diff的用法
diff后面可以接两个文件名或两个目录名。 如果是一个目录名加一个文件名,那么只作用在那么个目录下的同名文件。
如果是两个目录的话,作用于该目录下的所有文件,不递归。如果我们希望递归执行,需要使用-r参数。
命令diff A B >C ,一般A是原始文件,B是修改后的文件,C称为A的补丁文件。
不加任何参数生成的diff文件格式是一种简单的格式,这种格式只标出了不一样的行数和内容。我们需要一种更详细的格式,可以标识出不同之处的上下文环境,这样更有利于提高patch命令的识别能力。这个时候可以用-c开关。
2. patch的用法
patch用于根据原文件和补丁文件生成目标文件。还是拿上个例子来说
patch A C 就能得到B, 这一步叫做对A打上了B的名字为C的补丁。
之一步之后,你的文件A就变成了文件B。如果你打完补丁之后想恢复到A怎么办呢?
patch -R B C 就可以重新还原到A了。
所以不用担心会失去A的问题。
其实patch在具体使用的时候是不用指定原文件的,因为补丁文件中都已经记载了原文件的路径和名称。
patch足够聪明可以认出来。但是有时候会有点小问题。比如一般对两个目录diff的时候可能已经包含了原目录的名字,但是我们打补丁的时候会进入到目
录中再使用patch,着个时候就需要你告诉
patch命令怎么处理补丁文件中的路径。可以利用-pn开关,告诉patch命令忽略的路径分隔符的个数。举例如下:
A文件在 DIR_A下,修改后的B文件在DIR_B下,一般DIR_A和DIR_B在同一级目录。我们为了对整个目录下的所有文件一次性diff,我们一般会到DIR_A和DIR_B的父目录下执行以下命令
diff -rc DIR_A DIR_B >C
这个时候补丁文件C中会记录了原始文件的路径为 DIR_A/A
现在另一个用户得到了A文件和C文件,其中A文件所在的目录也是DIR_A。 一般,他会比较喜欢在DIR_A目录下面进行patch操作,它会执行
patch
但是这个时候patch分析C文件中的记录,认为原始文件是./DIR_A/A,但实际上是./A,此时patch会找不到原始文件。为了避免这种情况我们可以使用-p1参数如下
patch -p1
此时,patch会忽略掉第1个”/”之前的内容,认为原始文件是 ./A,这样就正确了。
最后有以下几点注意:
1. 一次打多个patch的话,一般这些patch有先后顺序,得按次序打才行。
2. 在patch之前不要对原文件进行任何修改
3. 如果patch中记录的原始文件和你得到的原始文件版本不匹配(很容易出现),那么你可以尝试使用patch, 如果幸运的话,可以成功。大部分情况下,会有不匹配的情况,此时patch会生成rej文件,记录失败的地方,你可以手工修改。v
现在回想起来,这个问题是显而易见的。但是平心而论,与其说这是一个天才的灵感的闪现,还不如说这是自信心的体现。我开发出rn的第一个版本,然后继续为
它编写补丁,这整个事情就是一团乱麻。你不可能强制用户使用补丁,因为他们可以手工完成这些工作。因此,他们就会省略一些自己认为不必要的工作,他们把新
的修改加诸于原来的程序之上,因此而使得程序混乱。我编写补丁,这样就没有人找借口说这很难了。
我不清楚是否事实就是如此,但是多年以来,我一直对别人讲patch对于计算机文化的影响比rn和Perl的影响都要大。现在Internet的速度比原
来有大幅度的提高,把整个发行版本分散到世界各地也变得更加简单,似乎只有在开发者之间才需要传送补丁。我已经很多年没有传送Perl的patch工具包
了。我认为虽然patch整体上的重要性在逐渐降低,但是仍然是开发者交流思想的一种方法。但是就那一段时间而言,patch真正在相当大的程度上都影响
了软件的开发方式。 Larry
Wall针对patch对于计算机业界总体上重要性正在降低的评价可能是正确的,但是在自由软件世界中,patch仍然是一种必不可少的工具。无处不在的
patch使得新手和非程序员能够简单地参与软件的alpha测试和beta测试,这对于整个计算机业界是十分有益的。
在我留意到在Linux内核邮件列表中会周期性的出现这样一件事情时就产生了写这篇文章的念头。大约每三个月就会有人张贴要求把Linux内核源代码的发
行版本独立出来的文章,据说这是因为有些人只对i386的代码和IDE的磁盘驱动感兴趣,他们并不想为每个内核发行版本都下载Alpha、Sparc等等
的文件和众多的SCSI驱动程序。这篇文章后面紧跟的是一些耐心的回复文章(有些文章则并没有耐心),大部分文章都是在讨论原来的使用有关patch来更
新内核源代码。接着Linus
Torvalds就会再次声明自己没有兴趣投身于这种把内核源程序切割成小块的繁杂的劳动,但是如果有人愿意,他们可以自由地开展这项独立的工程。到现在
为止都没有志愿者出现。我并不想谴责那些内核黑客不能耐心等待,却把生活变得复杂;我猜想直接使用内核工作可能比检查整个内核的发行版本方案要更加有趣、
更富有挑战性。下载11M的内核源程序包可是件非常耗费时间的事情(对于那些按照时间上网的人来说,这是很昂贵的),但是内核patch才只有几十K大
小,很少会超过1M。我的硬盘上的2.1.99开发内核源程序经过patch的升级,已经升级到了2.1.119版本,我怀疑如果我紧随内核的发展而不断
升级,那么也许我就要完整地下载每一个发行版本了。
使用patch
patch附带有一个很好的帮助,其中罗列了很多选项,但是99%的时间只要两个选项就能满足我们的需要:
patch -p1 < [patchfile]
patch -R < [patchfile] (used to undo a patch) |
-p1选项代表patchfile中文件名左边目录的层数,顶层目录在不同的机器上有所不同。要使用这个选项,就要把你的patch放在要被打补丁的目录
下,然后在这个目录中运行path -p1 < [patchfile]。来自Linux内核patch的一个简短的引用可以这样实现:
diff
-u --recursive --new-file v2.1.118/linux/mm/swapfile.c
linux/mm/swapfile. c--- v2.1.118/linux/mm/swapfile.c Wed Aug 26
11:37:45 1998 +++ linux/mm/swapfile.c Wed Aug 26 16:01:57 1998 @@
-489,7 +489,7 @@ int swap_header_version; int lock_map_size =
PAGE_SIZE; int nr_good_pages = 0; - char tmp_lock_map = 0; + unsigned
long tmp_lock_map = 0; |
应用来自本段中使用-p1开关拷贝的patch可以有效地减短patch定位的路径;patch会查找当前目录下一个名为/mm的子目录,接着应该会在这
儿发现swapfile.c文件,然后等待打补丁。在这个过程中,以破折号(“-”号,译者注)开始的行会被一个以加号(“+”号,译者注)开始的行代
替。一个典型的patch会包含对多个文件的更新,每个部分中都由对两个版本的文件运行diff -u命令的输出结果组成。
patch在操作时把自己的输出结果显示在屏幕上,但是这种输出通常都滚屏太快,来不及观看。原来准备patch的文件名为*.orig,新的patch
文件会覆盖这个初始文件名。
打补丁的问题 使用不同版本的patch问题来源可能不同,所有的版本在网络上都是可用的。Larry
Wall近年来已经不再做很多工作来更新patch了,这可能是由于他最后发行的一个版本在大部分情况下都能正常运行。最近几年以来,一直是GNU项目的
FSF程序员发行新版本的patch。他们首先修订有问题的patch,但是我最近一直使用没有问题的2.5版本(这是Debian2.0的发行版本
号)。过去,我的2.1版本也一直运行的很好。当前的GNU patch的版本可以从GNU
FTP站点上获取,然而大部分人都只使用他们Linux发行版中所提供的版本。
让我们假定你已经对一个目录下的源程序文件进行了patch修补工作,但是patch并没有清晰地发挥作用。这可能会偶然发生,在打补丁的过程中会显示错
误信息,其中带有行号,说明哪一个文件出现了问题。有时错误是很明显的,例如缺少了分号,这种错误可以不费多大力气就能改正。另外一种可能是从patch
部分删除了产生问题的部分,但是这样根据所涉及到的文件的不同可能会正常工作,也可能不能正常工作了。
另外一种常见的错位为:假设你有一个未使用tar打包的内核源程序文件,在/linux/arch/下浏览各个子目录时你会发现各种机器体系结构子目录,
例如alpah、sparc等等。如果你和大多数Linux用户一样,使用的是Intel的处理器(或者是Intel系列),你可以决定删除这些目录,这
些目录对于编译你特殊的内核并不需要,只是白白占用了磁盘空间。一段时间之后发行了一个新的内核patch,此时试图进行patch操作,当它发现不能找
到自己打补丁需要的Alpha或者PPC文件,就会停顿下来。幸运的是patch在这些地方允许用户参与,它会询问"Skip this
patch?"回答"y",patch就可以按照正确的路径继续执行。也许你需要回答这个问题很多次,因此允许自己不需要的目录保留在磁盘上是一种很好的
方法。
给内核打补丁的技巧
很多Linux用户使用patch都主要是给内核源程序打补丁,因此有一些技巧可以使用。可能最简单的方法是使用shell脚本给内核打补丁,这可以在内
核源程序树中的/scripts子目录中找到。这种方便的、编写良好的脚本是由Nick Holloway在1995年编写的;两年以后,Adam
Sulmicki增加了多种压缩格式的支持,包括*.bz、*.bz2、compress、gzip和无格式文本(也就是已经解压的patch)。这个脚
本假定在你使用新版本的patch时,你的内核源程序是在/usr/src/linux目录中。这些缺省值可以通过这种格式的命令行开关覆盖:patch
-kernel [sourcedir [patchdir]
]。如果任何一部分的patch失败,对内核打补丁的过程都会失败,但是如果patch清晰地起作用,它就会调用find,这会删除所有的patch留下
的*.orig文件。
如果你准备查看命令的输出,或者可能你希望保留*.orig文件直到你确定打过补丁的源程序编译已经通过,按照我的经验,直接运行patch(正如前面介
绍的一样,patch位于内核源程序的最高目录)是很可靠的。为了避免对patch进行解压,在使用之前,可以使用这样一个技巧: gzip -cd
patchXX.gz | patch -p1 或者 bzip2 -dc patchXX.bz2 | patch -p1
在使用patch之后,可以使用find程序来检测被拒绝的文件: find . -name *.rej
第一次使用这个命令,语法可能有些不清楚。点号(“.”)说明find应该查找当前目录并递规查找当前目录之下的所有子目录。记住,点号前后都应该有一个
空格。通配符"*"号前面的反斜线把星号转义出来,以免shell会搞混,星号是有其它意义的。如果find找到了任何的*.rej文件,它就会把文件名
打印到屏幕上。如果没有任何输出find就退出了,那么就差不多能确定patch正确发挥作用了。 find的另外一个工作是删除*.orig文件:
find . -name *.orig -print0 | xargs -0r rm -f
这个命令敲起来相当麻烦,可以使用一个新的shell别名来代替这个命令。在你的~/.bashrc文件中类似这样的一行: alias
findorig find . -name *.orig -print0 | xargs -0r rm -f
可以允许你只输入findorig就可以调用前面的命令。如果别名命令的定义中包含空格,那么就必须使用单引号。为了不用先退出再重新登陆就可以使用一个
新的别名,可以在命令行中敲如~/.bashrc。
附加内容和结束语
在撰写本文时,我刚好把自己的机器从2.1版本使用patch升级到了2.5版本。这两个版本都是来自现在的FSF/GNU维护人员。马上我就注意到
2.5版本默认的输出已经改变了,屏幕上显示的信息变少了。原来在patch检测进行修补的行号时显示的Larry
Wall的"...hmm"不见了。2.5版本的输出只剩下诸如"patching file
[filename]"之类的信息了,而没有早期版本显示的那么多信息了。无可否认,信息滚屏太快,根本无法阅读,但是输出可以重定向到一个文件中供以后
使用。这种变化不会影响程序的功能,但是减少了人为的成分。在我看来,使用诸如原来的"...hmm"信息和源代码中的注释一样,对于提醒用户程序是显示
执行的工作的结果是很有价值的,这就像人在呼吸一样,而不应该使用一些毫无结果的位集合。通过对patch命令行增加--verbose开关可以恢复原来
的显示内容,但是我相信很多用户既不会注意到这个选项,也不会不辞辛劳地输入这个选项。2.1和2.5版本的另外一个不同是除非patch给出了-b选
项,否则不能创建*.orig备份文件。
对于那些对软件和内核"前沿"bug报告测试和提供测试报告不感兴趣的人来说,patch并不是必须的,但是通常大部分Linux世界里有趣的开发都是属
于这个范畴的。获得patch的使用方式并不困难,这种努力可以得到充分的回报。
(译者注:由于水平有限,错误疏漏之处在所难免,如果你认为本文翻译的有问题,可以对其进行修改,请将修改后的文章给译者()发送一份。)