Linux下补丁的介绍及使用

时间:2022-02-18 14:05:19

Linux下补丁的介绍及使用

首先介绍一下diff和patch。在这里不会把man在线文档上所有的选项都介绍一下,那样也没有必要。在99%的时间里,我们只会用到几个选项。所以必须学会这几个选项。

1、diff

NAME

diff - find differences between two files

 

SYNOPSIS

diff [options] from-file to-file

 

简单的说,diff的功能就是用来比较两个文件的不同,然后记录下来,也就是所谓的diff补丁。语法格式:diff 【选项】 源文件(夹) 目的文件(夹),就是要给源文件(夹)打个补丁,使之变成目的文件(夹),术语也就是“升级”。下面介绍三个最为常用选项:

-r 是一个递归选项,设置了这个选项,diff会将两个不同版本源代码目录中的所有对应文件全部都进行一次比较,包括子目录文件。

-N 选项确保补丁文件将正确地处理已经创建或删除文件的情况。

-u 选项以统一格式创建补丁文件,这种格式比缺省格式更紧凑些。

 

diff是Unix系统的一个很重要的工具程序。

它用来比较两个文本文件的差异,是代码版本管理的基石之一。你在命令行下,输入:

  $ diff <变动前的文件> <变动后的文件>

diff就会告诉你,这两个文件有何差异。它的显示结果不太好懂,下面我就来说明,如何读懂diff。

一、diff的三种格式

由于历史原因,diff有三种格式:

  * 正常格式(normal diff)

  * 上下文格式(context diff)

  * 合并格式(unified diff)

我们依次来看。

二、示例文件

为了便于讲解,先新建两个示例文件。

第一个文件叫做f1,内容是每行一个a,一共7行。

a
  a
  a
  a
  a
  a
  a

第二个文件叫做f2,修改f1而成,第4行变成b,其他不变。

a
  a
  a
  b
  a
  a
  a

三、正常格式的diff

现在对f1和f2进行比较:

  $ diff f1 f2

这时,diff就会显示正常格式的结果:

  4c4
  < a
  ---
  > b

第一行是一个提示,用来说明变动位置。

  4c4

它分成三个部分:前面的"4",表示f1的第4行有变化;中间的"c"表示变动的模式是内容改变(change),其他模式还有"增加"(a,代表addition)和"删除"(d,代表deletion);后面的"4",表示变动后变成f2的第4行。

第二行分成两个部分。

  < a

前面的小于号,表示要从f1当中去除该行(也就是第4行),后面的"a"表示该行的内容。

第三行用来分割f1和f2。

  ---

第四行,类似于第二行。

  > b

前面的大于号表示f2增加了该行,后面的"b"表示该行的内容。

最早的Unix(即AT&T版本的Unix),使用的就是这种格式的diff。

 

四、上下文格式的diff

上个世纪80年代初,加州大学伯克利分校推出BSD版本的Unix时,觉得diff的显示结果太简单,最好加入上下文,便于了解发生的变动。因此,推出了上下文格式的diff。

它的使用方法是加入c参数(代表context)。

  $ diff -c f1 f2

显示结果如下:

  *** f1 2012-08-29 16:45:41.000000000 +0800
  --- f2 2012-08-2916:45:51.000000000 +0800
  ***************
  *** 1,7 ****
   a
   a
   a
  !a
   a
   a
   a
  --- 1,7 ----
   a
   a
   a
  !b
   a
   a
   a

这个结果分成四个部分。

第一部分的两行,显示两个文件的基本情况:文件名和时间信息。

  *** f1 2012-08-29 16:45:41.000000000 +0800
  --- f2 2012-08-2916:45:51.000000000 +0800

"***"表示变动前的文件,"---"表示变动后的文件。

第二部分是15个星号,将文件的基本情况与变动内容分割开。

  ***************

第三部分显示变动前的文件,即f1。

  *** 1,7 ****
   a
   a
   a
  !a
   a
   a
   a

这时不仅显示发生变化的第4行,还显示第4行的前面三行和后面三行,因此一共显示7行。所以,前面的"*** 1,7 ****"就表示,从第1行开始连续7行。

另外,文件内容的每一行最前面,还有一个标记位。如果为空,表示该行无变化;如果是感叹号(!),表示该行有改动;如果是减号(-),表示该行被删除;如果是加号(+),表示该行为新增。

第四部分显示变动后的文件,即f2。

  --- 1,7 ----
   a
   a
   a
  !b
   a
   a
   a

除了变动行(第4行)以外,也是上下文各显示三行,总共显示7行。

 

五、合并格式的diff

如果两个文件相似度很高,那么上下文格式的diff,将显示大量重复的内容,很浪费空间。1990年,GNU diff率先推出了"合并格式"的diff,将f1和f2的上下文合并在一起显示。

它的使用方法是加入u参数(代表unified)。

  $ diff -u f1 f2

显示结果如下:

---f1 2012-08-29 16:45:41.000000000 +0800
  +++ f2 2012-08-2916:45:51.000000000 +0800
  @@ -1,7 +1,7 @@
   a
   a
   a
  -a
  +b
   a
   a
   a

它的第一部分,也是文件的基本信息。

---f1 2012-08-29 16:45:41.000000000 +0800
  +++ f2 2012-08-2916:45:51.000000000 +0800

"---"表示变动前的文件,"+++"表示变动后的文件。

第二部分,变动的位置用两个@作为起首和结束。

  @@ -1,7 +1,7 @@

前面的"-1,7"分成三个部分:减号表示第一个文件(即f1),"1"表示第1行,"7"表示连续7行。合在一起,就表示下面是第一个文件从第1行开始的连续7行。同样的,"+1,7"表示变动后,成为第二个文件从第1行开始的连续7行。

第三部分是变动的具体内容。

   a
   a
   a
  -a
  +b
   a
   a
   a

除了有变动的那些行以外,也是上下文各显示3行。它将两个文件的上下文,合并显示在一起,所以叫做"合并格式"。每一行最前面的标志位,空表示无变动,减号表示第一个

文件删除的行,加号表示第二个文件新增的行。

 

六、git格式的diff

版本管理系统git,使用的是合并格式diff的变体。

  $ git diff

显示结果如下:

  diff --git a/f1 b/f1
  index 6f8a38c..449b072 100644
  --- a/f1
  +++ b/f1
  @@ -1,7 +1,7 @@
   a
   a
   a
  -a
  +b
   a
   a
   a

第一行表示结果为git格式的diff。

  diff --git a/f1 b/f1

进行比较的是,a版本的f1(即变动前)和b版本的f1(即变动后)。

第二行表示两个版本的git哈希值(index区域的6f8a38c对象,与工作目录区域的449b072对象进行比较),最后的六位数字是对象的模式(普通文件,644权限)。

  index 6f8a38c..449b072 100644

第三行表示进行比较的两个文件。

  --- a/f1
  +++ b/f1

"---"表示变动前的版本,"+++"表示变动后的版本。

后面的行都与官方的合并格式diff相同。

  @@ -1,7 +1,7 @@
   a
   a
   a
  -a
  +b
   a
   a
   a

 

2、patch

NAME

patch- apply a diff file to an original

 

SYNOPSIS

patch[options] [originalfile [patchfile]]

 

butusually just

patch-pnum

 

简单的说,patch就是利用diff制作的补丁来实现源文件(夹)和目的文件(夹)的转换。这样说就意味着你可以有源文件(夹)――>目的文件(夹),也可以目的文件(夹)――>源文件(夹)。下面介绍几个最常用选项:

-p0 选项要从当前目录查找目的文件(夹)

-p1 选项要忽略掉第一层目录,从当前目录开始查找。

 

在这里以实例说明:

---old/modules/pcitable Mon Sep 27 11:03:56 1999

+++new/modules/pcitable Tue Dec 19 20:05:41 2000

如果使用参数-p0,那就表示从当前目录找一个叫做old的文件夹,在它下面寻找modules下的pcitable文件来执行patch操作。

如果使用参数-p1,那就表示忽略第一层目录(即不管old),从当前目录寻找modules的文件夹,在它下面找pcitable。这样的前提是当前目录必须为modules所在的目录。而diff补丁文件则可以在任意位置,只要指明了diff补丁文件的路径就可以了。当然,可以用相对路径,也可以用绝对路径。不过我一般习惯用相对路径。

-E 选项说明如果发现了空文件,那么就删除它

-R 选项说明在补丁文件中的“新”文件和“旧”文件现在要调换过来了(实际上就是给新版本打补丁,让它变成老版本)

 

下面结合具体实例来分析和解决,分为两种类型:为单个文件打补丁和为文件夹内的多个文件打补丁。

 

环境:在RedHat 9.0下面以armlinux用户登陆。

目录树如下:

|--bootloader

|--debug

|--images

|--kernel

|--program

|--rootfiles

|--software

|--source

|--sysapps

|--tmp

`--tools

下面在program文件夹下面建立patch文件夹作为实验用,然后进入patch文件夹。

 

一、为单个文件进行补丁操作

 

1、建立测试文件test0、test1

 

[armlinux@lqmpatch]$ cat >>test0<

>111111

>111111

>111111

>EOF

[armlinux@lqmpatch]$ more test0

111111

111111

111111

[armlinux@lqmpatch]$ cat >>test1<

>222222

>111111

>222222

>111111

>EOF

[armlinux@lqmpatch]$ more test1

222222

111111

222222

111111

 

2、使用diff创建补丁test1.patch

[armlinux@lqmpatch]$ diff -uN test0 test1 >test1.patch

【注:因为单个文件,所以不需要-r选项。选项顺序没有关系,即可以是-uN,也可以是-Nu。】

[armlinux@lqmpatch]$ ls

test0test1 test1.patch

 

patch文件的结构

补丁头

补丁头是分别由---/+++开头的两行,用来表示要打补丁的文件。

---开头表示旧文件,

+++开头表示新文件。

 

一个补丁文件中的多个补丁

一个补丁文件中可能包含以---/+++开头的很多节,每一节用来打一个补丁。所以在一个补丁文件中可以包含好多个补丁。

 

块是补丁中要修改的地方。它通常由一部分不用修改的东西开始和结束。他们只是用来表示要修改的位置。他们通常以@@开始,结束于另一个块的开始或者一个新的补丁头。

 

块的缩进

块会缩进一列,而这一列是用来表示这一行是要增加还是要删除的。

 

块的第一列

+号表示这一行是要加上的。

-号表示这一行是要删除的。

没有加号也没有减号表示这里只是引用的而不需要修改。

 

[armlinux@lqmpatch]$ patch -p0 < test1.patch

patchingfile test0

[armlinux@lqmpatch]$ ls

test0test1 test1.patch

[armlinux@lqmpatch]$ cat test0

222222

111111

222222

111111

 

3、可以去除补丁,恢复旧版本

[armlinux@lqmpatch]$ patch -RE -p0 < test1.patch

patchingfile test0

[armlinux@lqmpatch]$ ls

test0test1 test1.patch

[armlinux@lqmpatch]$ cat test0

111111

111111

111111

 

二、为多个文件进行补丁操作

 

1、创建测试文件夹

[armlinux@lqmpatch]$ mkdir prj0

[armlinux@lqmpatch]$ cp test0 prj0

[armlinux@lqmpatch]$ ls

prj0test0 test1 test1.patch

[armlinux@lqmpatch]$ cd prj0/

[armlinux@lqmprj0]$ ls

test0

[armlinux@lqmprj0]$ cat >>prj0name<

>--------

>prj0/prj0name

>--------

>EOF

[armlinux@lqmprj0]$ ls

prj0nametest0

[armlinux@lqmprj0]$ cat prj0name

--------

prj0/prj0name

--------

[armlinux@lqmprj0]$ cd ..

[armlinux@lqmpatch]$ mkdir prj1

[armlinux@lqmpatch]$ cp test1 prj1

[armlinux@lqmpatch]$ cd prj1

[armlinux@lqmprj1]$ cat >>prj1name<

>---------

>prj1/prj1name

>---------

>EOF

[armlinux@lqmprj1]$ cat prj1name

---------

prj1/prj1name

---------

[armlinux@lqmprj1]$ cd ..

 

2、创建补丁

[armlinux@lqmpatch]$ diff -uNr prj0 prj1 > prj1.patch

 [armlinux@lqmpatch]$ ls

prj0prj1 prj1.patch test0 test1 test1.patch

[armlinux@lqmpatch]$ cp prj1.patch ./prj0

[armlinux@lqmpatch]$ cd prj0

[armlinux@lqmprj0]$ patch -p1 < prj1.patch

patchingfile prj0name

patchingfile prj1name

patchingfile test0

patchingfile test1

[armlinux@lqmprj0]$ ls

prj1nameprj1.patch test1

[armlinux@lqmprj0]$ patch -R -p1 < prj1.patch

patchingfile prj0name

patchingfile prj1name

patchingfile test0

patchingfile test1

[armlinux@lqmprj0]$ ls

prj0nameprj1.patch test0

-------------------

 

总结一下:

单个文件

diff–uN from-file to-file >to-file.patch

patch–p0 < to-file.patch

patch–RE –p0 < to-file.patch

 

多个文件

diff–uNr from-docu to-docu >to-docu.patch

patch–p1 < to-docu.patch

patch–R –p1

-------------------

 

三、应用

为内核打补丁。

1、首先是解压,因为发布的补丁文件都是使用gzip压缩的。

$gunzip../setup-dir/ patch-2.4.21-rmk1.gz

 

2、然后进入你的内核源代码目录

$cdlinux-2.4.21

 

3、打补丁

$patch–p1 < ../../setup-dir/patch-2.4.21-rmk1

 

打完补丁后,需要检查一下有没有拒绝执行的文件,即检查.rej文件的存在。使用命令:

$find. -name *.rej

如果发现,会将其输出到标准输出终端,默认屏幕。当然,你也可以采用重定向,输出到指定文件,比如reject。

$fine. -name *.rej >reject

然后可以查看reject的内容了。

 

diff和patch使用指南

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< C

 

但是这个时候patch分析C文件中的记录,认为原始文件是./DIR_A/A,但实际上是./A,此时patch会找不到原始文件。为了避免这种情况我们可以使用-p1参数如下

 

patch-p1 < C

 

此时,patch会忽略掉第1个”/”之前的内容,认为原始文件是 ./A,这样就正确了。

 

最后有以下几点注意:

 

1. 一次打多个patch的话,一般这些patch有先后顺序,得按次序打才行。

2. 在patch之前不要对原文件进行任何修改

3. 如果patch中记录的原始文件和你得到的原始文件版本不匹配(很容易出现),那么你可以尝试使用patch, 如果幸运的话,可以成功。大部分情况下,会有不匹配的情况,此时patch会生成rej文件,记录失败的地方,你可以手工修改。