Linux之旅(1): diff, patch和quilt (上)

來源:互聯網
上載者:User
Linux之旅(1): diff, patch和quilt (上)

diff和patch是在Linux環境為原始碼製作和應用補丁的標準工具。diff可以比較檔案或目錄的差異,並將差異記錄到補丁檔案。patch可以將補丁檔案應用到原始碼上。quilt也是一個製作和應用補丁的工具,它適合於管理較多補丁。quilt有自己的特有的工作方式。本文通過簡單的例子介紹這三個常用的工具。

0 樣本工程

我們先準備一個用來做實驗的工程,它包含若干子目錄和檔案。可以用find命令列出檔案清單:

$ find old-prj/ -type f
old-prj/inc/def1.h
old-prj/inc/def2.h
old-prj/src/sys/sys1.c
old-prj/src/sys/sys1.h
old-prj/src/app/app1.c
old-prj/src/app/app2.c
old-prj/src/app/app2.h
old-prj/src/app/app1.h
old-prj/src/drv/drv1.h
old-prj/src/drv/drv2.c
old-prj/src/drv/drv1.c
old-prj/src/drv/drv2.h
old-prj/build/Makefile

find命令的"-type f"參數選擇普通檔案,可以省略掉目錄。希望自己操作的讀者可以下載這個樣本工程。

1 diff和patch1.1 比較一個檔案

將old-prj.tar.bz2放到我們的工作目錄,然後建立一個子目錄,進入後解壓樣本工程:

$ mkdir test1; cd test1; tar xvjf ../old-prj.tar.bz2

用分號分隔多個命令可以節省篇幅。將old-prj複製到new-prj:

$ cp -a old-prj/ new-prj

讓我們編輯一個檔案。src/drv/drv1.h的內容本來是:

$ cat -n old-prj/src/drv/drv1.h
     1  #ifndef DRV1_H
     2  #define DRV1_H
     3
     4  #include "def1.h"
     5
     6  typedef struct {
     7    int p1;
     8    int p2;
     9    int p3;
    10  } App1;
    11
    12  void do_app1(void);
    13
    14  #endif

cat命令的"-n"參數可以增加行號。我們用vi將它修改成:

$ cat -n new-prj/src/drv/drv1.h
     1  #ifndef DRV1_H
     2  #define DRV1_H
     3
     4  #include "def1.h"
     5
     6  typedef struct {
     7    int a;
     8    int b;
     9  } App1;
    10
    11  void do_app1(void);
    12
    13  #endif

現在可以用diff命令比較檔案了:

$ diff -u old-prj/src/drv/drv1.h new-prj/src/drv/drv1.h
--- old-prj/src/drv/drv1.h      2008-03-01 12:59:46.000000000 +0800
+++ new-prj/src/drv/drv1.h      2008-03-01 13:07:14.000000000 +0800
@@ -4,9 +4,8 @@
 #include "def1.h"
 
 typedef struct {
-  int p1;
-  int p2;
-  int p3;
+  int a;
+  int b;
 } App1;
 
 void do_app1(void);

diff程式按行比較文字檔。比較檔案的diff命令格式是:

$ diff -u 舊檔案 新檔案

"-u"參數指定diff命令使用 unified 格式,這是一種最常用的格式,我們來看看它的含義。

1.2 diff的 unified 格式

以"---"開頭的行是舊檔案資訊,以"+++"開頭的行是新檔案資訊:

--- old-prj/src/drv/drv1.h      2008-03-01 12:59:46.000000000 +0800
+++ new-prj/src/drv/drv1.h      2008-03-01 13:07:14.000000000 +0800

unified 格式預設在變化部分的前後各顯示三行上下文。在上例中,舊檔案的7、8、9行被替換成新檔案的7、8行。舊檔案的變化部分是7-9行,前後多顯示3行,因此顯示4-12行。新檔案的變化部分是7-8行,前後多顯示3行,因此顯示4-11行。以"@@"包圍的行指示補丁的範圍:

@@ -4,9 +4,8 @@

'-4,9'中,'-'表示舊檔案,'4,9'表示從第4行開始,顯示9行,即顯示4-12行。'+4,8'中,'+'表示新檔案,'4,8'表示從第4行開始,顯示8行,即顯示4-11行。"@@"行之後是上下文和變化的文本,其中'-'開頭的行是舊檔案特有的,'+'開頭的行是新檔案特有的,其它行是兩個檔案都有的,即補丁的上下文。例如:

 #include "def1.h"
 
 typedef struct {
-  int p1;
-  int p2;
-  int p3;
+  int a;
+  int b;
 } App1;
 
 void do_app1(void);

1.3 製作和應用補丁

所謂製作補丁就是diff的輸出重新導向到一個檔案,這個檔案就是補丁檔案。例如:

$ diff -u old-prj/src/drv/drv1.h new-prj/src/drv/drv1.h>../drv1.diff

我們將old-prj解壓到另一個目錄,準備應用這個補丁:

$ cd ..; mkdir test2; cd test2; tar xvjf ../old-prj.tar.bz2; mv old-prj myprj; cd myprj

在真實情境中,test2目錄通常是在使用者2的電腦上。使用者2可能不使用 old-prj 作為第一級目錄的名字。例如:使用者1的第一級目錄名是 linux-2.6.23.14, 使用者2的第一級目錄名是linux。所以我們將 old-prj 改為 myprj 以類比這種情況。

我們在 myprj 目錄使用patch命令應用補丁:

$ patch -p1 < ../../drv1.diff
patching file src/drv/drv1.h

patch命令列中為什麼沒有出現要打補丁的檔案?這是因為patch命令可以使用補丁檔案中的檔案資訊:

--- old-prj/src/drv/drv1.h      2008-03-01 12:59:46.000000000 +0800

"-pn"參數(上例中n=1)中的n表示要從補丁檔案的檔案路徑中去掉幾層目錄,可以理解為去掉幾個'/'。例如:p1表示去掉一層目錄,"old-prj/src/drv/drv1.h"去掉一層就成為"src/drv/drv1.h"。patch命令在 myprj 目錄找到"src/drv/drv1.h"後應用補丁。

我們通常都在代碼樹的上一層目錄製作補丁,在代碼樹的根目錄應用補丁。因此,最常用的patch命令格式是:

$ patch -p1 < 補丁檔案

1.4 比較目錄

我們回到test1目錄,再對 new_prj 做一些改動。這次我們刪除掉src/sys目錄及其中的檔案。再建立src/usr目錄,並在該目錄增加兩個檔案usr1.h和usr1.c。

$ cd ../../test1; rm -rf new-prj/src/sys; mkdir new-prj/src/usr
$ echo -e "#ifndef USR1_H/n#define USR1_H/n#include /"def1.h/"/n#endif">new-prj/src/usr/usr1.h
$ echo -e "#include /"usr1.h/"">new-prj/src/usr/usr1.c

echo命令的"-e"參數開啟對轉義符的支援,bash預設是不支援轉義符的。

現在我們比較目錄並製作補丁:

$ diff -Nur old-prj/ new-prj/ > ../prj.diff

讀者可以cat這個補丁檔案的內容。根據前面的介紹,讀者應該能看懂補丁檔案了吧。

比較目錄的常用命令是:

$ diff -Nur 舊目錄 新目錄 > 補丁檔案

$ diff -Naur 舊目錄 新目錄 > 補丁檔案

"-u"參數前面已經介紹過了。"-N"參數將不存在的檔案當作空檔案。如果沒有這個參數,補丁就不會包含孤兒檔案(即另一方沒有的檔案)。"-r"參數表示比較子目錄。"-a"參數表示將所有檔案當作文字檔。

我們再準備一個目錄來應用補丁:

$ cd ..; mkdir test3; cd test3; tar xvjf ../old-prj.tar.bz2; mv old-prj myprj; cd myprj

在原始碼樹的根目錄應用補丁:

$ patch -p1 < ../../prj.diff
patching file src/drv/drv1.h
patching file src/sys/sys1.c
patching file src/sys/sys1.h
patching file src/usr/usr1.c
patching file src/usr/usr1.h

好了,讀者可以用"diff -Nur"比較一下"test1/new_prj"和"test3/myprj",沒有輸出就表示完全相同。

$ cd ../..; diff -Nur test1/new-prj test3/myprj

1.5 很多的補丁...

一個大項目可能有不同開發人員提供很多補丁。這些補丁可能還存在依賴關係,例如補丁B必須打在補丁A上。我們當然可以憑著程式員的“心細如髮”去管理好這些補丁,不過有一個叫quilt的工具可以使我們輕鬆一些。當然,即使有工具的協助,細心和認真也是必需的。 

附錄

為了簡單起見,前面只介紹了一個"diff -Nur 老目錄 新目錄"的用法。有時候,新目錄裡只放了修改過的檔案。這時可以不使用-N參數以忽略孤兒檔案,即"diff -ur 老目錄 新目錄"。diff會輸出孤兒檔案的提示,我們可以刪除或保留這些提示,它們對patch沒有影響。

使用diff時可以用--exclude排除檔案和目錄,例如:

diff -ur -exclude=.* --exclude=CVS prj_old prj_new

上例排除了原始碼樹中以'.'開頭的檔案和所有CVS目錄。其實對於CVS項目,可以直接在原始碼樹根目錄中執行:

cvs diff -u3 > 補丁檔案名稱

u3表示輸出3行內容相關的unified 格式。打補丁時在原始碼樹根目錄中執行:

patch -p0 < 補丁檔案名稱

"cvs diff"會自動忽略CVS項目外的檔案。通過CVS的tag和補丁檔案,我們可以方便地儲存工作快照。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.