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文件,記錄失敗的地方,你可以手工修改。