patch 程式

  • 為什麼 patch?
    1. 檔案( 版本 )之間的差異,可以指令 diff 儲存在一個 patch 檔案。
    2. 若舊版本需要修改,只要將 patch 檔案釋出。
    3. 使用者可以指令 patch,配合 patch 檔案中記錄之新舊版差異,將舊版程式更新。
    4. 使用者若發現並修正一個程式的臭蟲,簡單、正確的方式是,寄一個 patch 檔案給作者,而不要只是說明修正的地方。

  • diff 指令:比對兩個檔案之間的差異,一般是用在 ASCII 純文字檔的比對上。
    1. diff 指令用法:
      [root@linux ~]# diff [-bBiqn] from-file to-file
      選項:
      from-file :檔名,作為原始比對檔案的檔名;
      to-file   :檔名,作為目的比對檔案的檔名;
      # from-file 或 to-file 可以 - 取代, - 代表『Standard input』。
      
      -b :忽略一行當中,多個空白的差異
           (例如 "about me" 與 "about     me" 視為相同)
      -B :忽略空白行的差異。
      -i :忽略大小寫的不同。
      -q :只列出檔案是否有差異。
      -n :以 RCS 格式輸出檔案之差異。
      -c (-C NUM) :兩個檔案皆加入差異部分前後 NUM 行,以增加輸出之可讀性。預設 NUM=3。 
      -u (-U NUM) :加入差異部分前後 NUM 行,以增加輸出之可讀性。預設 NUM=3。
      
    2. 預處理:將 /etc/passwd 第四行刪除,第六行取代為『no six line』,新的檔案放到 /tmp/test。
      [root@linux ~]# mkdir -p /tmp/test
      [root@linux ~]# cat /etc/passwd | \
      > sed -e '4d' -e '6c no six line' > /tmp/test/passwd
      # sed 後面若要接超過兩個以上的動作時,每個動作前面得加 -e 。
      
    3. 比對 /tmp/test/passwd 與 /etc/passwd 的差異。
      [root@dywHome2 ~]# diff /etc/passwd /tmp/test/passwd
      4d3
      < adm:x:3:4:adm:/var/adm:/bin/sh
      6c5
      < sync:x:5:0:sync:/sbin:/bin/sync
      ---
      > no six line
      
    4. 只列出檔案是否有差異。
      [root@dywHome2 ~]# diff -q /etc/passwd /tmp/test/passwd
      Files /etc/passwd and /tmp/test/passwd differ
      
    5. 以 RCS 格式輸出檔案之差異。
      [root@dywHome2 ~]# diff -n /etc/passwd /tmp/test/passwd
      d4 1
      d6 1
      a6 1
      no six line
      
    6. 加入差異部分前後 3 行,以增加輸出之可讀性。
      [root@dywHome2 tmp]# diff -u old/ new/ > test.patch
      [root@dywHome2 tmp]# cat test.patch
      diff -u old/passwd new/passwd
      --- old/passwd  2008-04-16 13:14:50.000000000 -0400
      +++ new/passwd  2008-04-16 11:15:12.000000000 -0400
      @@ -1,9 +1,8 @@
       root:x:0:0:root:/root:/bin/bash
       bin:x:1:1:bin:/bin:/bin/sh
       daemon:x:2:2:daemon:/sbin:/bin/sh
      -adm:x:3:4:adm:/var/adm:/bin/sh
       lp:x:4:7:lp:/var/spool/lpd:/bin/sh
      -sync:x:5:0:sync:/sbin:/bin/sync
      +no six line
       shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
       halt:x:7:0:halt:/sbin:/sbin/halt
       mail:x:8:12:mail:/var/spool/mail:/bin/sh
      
    7. 比對 /etc 與 /tmp/test 的差異。
      [root@linux ~]# diff /etc /tmp/test
      ......(前面省略).....
      Only in /etc: paper.config
      diff /etc/passwd /tmp/test/passwd
      4d3
      < adm:x:3:4:adm:/var/adm:/sbin/nologin
      6c5
      < sync:x:5:0:sync:/sbin:/bin/sync
      ---
      > no six line
      Only in /etc: passwd-
      ......(後面省略).....
      

  • cmp:主要利用『位元』單位去比對(diff 主要是以『行』為單位比對,cmp 則是以『位元』為單位去比對)。
    1. cmp 指令用法:
      [root@linux ~]# cmp [-bcsi] file1 file2
      選項:
      -b :列出第一個的不同點之字元。
      -c :同上。
      -i SKIP1:SKIP2 : file1 與 file2 分別忽略前 SKIP1 與 SKIP2 位元。
      -s :安靜模式,不顯示任何訊息。
      
    2. 用 cmp 比較 /etc/passwd 與 /tmp/test/passwd
      [root@dywHome2 ~]# cmp /etc/passwd /tmp/test/passwd
      /etc/passwd /tmp/test/passwd differ: byte 94, line 4
      # 不同點在第四行,而且位元數是在第 94 個位元。
      
    3. 列出第一個的不同點之字元
      [root@dywHome2 ~]# cmp -c /etc/passwd /tmp/test/passwd
      /etc/passwd /tmp/test/passwd differ: byte 94, line 4 is 141 a 154 l
      # 不同點在檔案 /etc/passwd 的第一個字元為 a,在檔案 /tmp/test/passwd 的第一個字元為 l。
      # 字元 a 之八進位碼為 141,字元 l 之八進位碼為 154。
      
    4. 以 printf 驗證 ASCII 之字元
      [root@dywHome2 ~]# printf '\x61\t\x6c\n'
      a       l
      # \xNN : NN 為十六進位
      
  • patch:檔案補丁。需與 diff 配合使用。
    1. patch 指令用法
      [root@linux ~]# patch [OPTION]... [ORIGFILE [PATCHFILE]]
      選項:
      -pNUM :取消 NUM 層目錄。
       例如:假設檔名 /u/howard/src/blurfl/blurfl.c
            -p0 :代表  u/howard/src/blurfl/blurfl.c
            -p4 :代表  blurfl/blurfl.c
      -l     :忽略空白之差異。
      -i PATCHFILE :從 PATCHFILE 讀取補丁。
      -o FILE :輸出補丁到檔案 FILE。
      -r FILE :輸出錯誤到檔案 FILE。
      
    2. 預處理:建立兩個不同版本的檔案 /tmp/test/passwd 與 /etc/passwd。
      [root@dywHome2 ~]# mkdir /tmp/old; cp /etc/passwd /tmp/old
      [root@dywHome2 ~]# mkdir /tmp/new; cp /tmp/test/passwd /tmp/new
      
    3. 建立補丁檔案 /tmp/test.patch 記錄新舊檔案之間的差異。
      [root@dywHome2 ~]# cd /tmp ; diff old/ new/ > test.patch
      # diff 製作檔案時,舊的檔案必須是在前面,亦即是 diff oldfile newfile。
      
    4. 將舊的內容 (/tmp/old/passwd) 更新到新版 (/tmp/new/passwd) 的內容
      [root@dywHome2 tmp]# cd /tmp/old
      [root@dywHome2 old]# patch passwd -i /tmp/test.patch
      patching file passwd
      # 選項 -i 亦可省略
      
    5. 更新內容,並指定存於 passwd1
      [root@dywHome2 old]# patch passwd /tmp/test.patch -o passwd1
      patching file passwd
      
    6. 內容已更新,若再做一次補丁,系統會詢問是否執行?
      1. 預設回答 n(o):系統會將錯誤訊息存在 passwd.rej
        [root@dywHome2 old]# patch passwd -i /tmp/test.patch
        patching file passwd
        Reversed (or previously applied) patch detected!  Assume -R? [n] n
        Apply anyway? [n] n
        Skipping patch.
        2 out of 2 hunks ignored -- saving rejects to file passwd.rej
        
      2. 回答 y(es):系統會將更新後的內容存在 passwd.orig
        [root@dywHome2 old]# patch passwd -i /tmp/test.patch
        patching file passwd
        Reversed (or previously applied) patch detected!  Assume -R? [n] y
        
      3. 查詢檔案
        [root@dywHome2 old]# ll passwd*
        -rw-r--r-- 1 root root 1207 Apr 16 13:14 passwd
        -rw------- 1 root root 1156 Apr 16 13:10 passwd1
        -rw-r--r-- 1 root root 1156 Apr 16 13:11 passwd.orig
        -rw-r--r-- 1 root root  149 Apr 16 13:12 passwd.rej
        
    7. 使用選項 -pNUM 更新舊版資料
      1. 變換目錄至 /tmp
        [root@dywHome2 old]# cd ..
        
      2. 以選項 -u 建立 目錄 old/ 與 new/ 下檔案之差異檔,再回到目錄 /tmp/old。
        [root@dywHome2 tmp]# diff -u old/ new/ > /tmp/test.patch
        [root@dywHome2 tmp]# cd old
        
      3. test.patch 儲存 diff -u old/passwd new/passwd,故必須一層目錄,即 -p1。
        [root@dywHome2 old]# patch -p1 </tmp/test.patch
        patching file passwd
        
      4. 若使用 -p0,則無法找到要更新的檔案,系統會要求輸入要更新的檔案。
        [root@dywHome2 old]# patch -p0 </tmp/test.patch
        can't find file to patch at input line 4
        Perhaps you used the wrong -p or --strip option?
        The text leading up to this was:
        --------------------------
        |diff -u old/passwd new/passwd
        |--- old/passwd 2008-04-16 14:46:21.000000000 -0400
        |+++ new/passwd 2008-04-16 11:15:12.000000000 -0400
        --------------------------
        File to patch:
        
      5. 命令 patch -pnum <patchfile> 適用於目錄下多個檔案,較接近實際狀況。

  • 範例:利用 patch 命令及第一版檔案、第一版和第二版的差異檔案,產生完整的第二版檔案。
    1. 第一版的檔案 file1.c:
      This is file one
      line 2
      line 3
      there is no line 4, this is line 5
      line 6
      
    2. 產生第二版的檔案 file2.c:
      This is file two
      line 2
      line 3
      line 4
      line 5
      line 6
      a new line 8
      
    3. 利用 diff 命令產生差異:
      $ diff file1.c file2.c > diffs
      diffs 檔案如下:
      1c1
      < This is file one
      —
      > This is file two
      4c4,5
      < there is no line 4, this is line 5
      —
      > line 4
      > line 5
      5a7
      > a new line 8
      
    4. 假設有 file1.c 和 diffs 檔案。可以利用 patch 命令更新 file1.c,使其與 file2.c 一樣。
      $ patch file1.c diffs
      Hmm... Looks like a normal diff to me...
      Patching file file1.c using Plan A...
      Hunk #1 succeeded at 1.
      Hunk #2 succeeded at 4.
      Hunk #3 succeeded at 7.
      done
      $
      
    5. 若希望回覆 file1.c ,只要再使用 patch,加上-R(反向 patch)選項,file1.c 就會回到原本的狀況。。
      $ patch -R file1.c diffs
      Hmm... Looks like a normal diff to me...
      Patching file file1.c using Plan A...
      Hunk #1 succeeded at 1.
      Hunk #2 succeeded at 4.
      Hunk #3 succeeded at 6.
      done
      $
      
  • 例題:完成下列工作。
    1. 以 vi 在 printf.txt 最後加入一行 csie - - -,並另存為 printf.new;
    2. 以 diff 比較 printf.txt 及 printf.new,並建立其 patch file,printf.patch;
    3. 以 cmp 比較 printf.txt 及 printf.new;
    4. 利用 patch file,printf.patch 將 printf.txt 更新為 printf.new。
練習題
  1. 使用開放原始碼之套件時,若發現並修正一個程式的臭蟲,要如何以一個簡單、正確的方式通知者?
    Sol. 寄一個 patch 檔案給作者,而不要只是說明修正的地方。
  2. 請以指令 cat 及 sed 配合管線處理,將 /etc/passwd 第四行刪除,第六行取代為『no six line』,新的檔案存為 /tmp/passwd。
    Sol. cat /etc/passwd | sed -e '4d' -e '6c no six line' > /tmp/passwd
  3. 如何以指令 diff 比對兩個檔案 file1 與 file2 之差異?
    Sol. diff file1 file2
  4. 以 diff 比對 file1 與 file2,出現訊息 4d3,代表意義為何?
    Sol. file1 第四行被刪除
  5. 以 diff 比對 file1 與 file2,出現訊息 6c5,代表意義為何?
    Sol. file1 第六行被取代成 file2 的第五行
  6. 如何以指令 diff 比對兩個檔案 file1 與 file2,且只列出檔案是否有差異?
    Sol. diff -q file1 file2
  7. 如何以指令 diff 比對兩個檔案 file1 與 file2,且忽略一行當中,多個空白的差異?
    Sol. diff -b file1 file2
  8. 如何以指令 diff 比對兩個檔案 file1 與 file2,且忽略空白行的差異?
    Sol. diff -B file1 file2
  9. 如何以指令 diff 比對兩個檔案 file1 與 file2,且忽略大小寫的不同?
    Sol. diff -i file1 file2
  10. 如何以指令 diff 比對兩個檔案 file1 與 file2,且以 RCS 格式輸出檔案之差異?
    Sol. diff -n file1 file2
  11. 如何以 diff 比對兩目錄 /tmp/old 與 /tmp/new,並將新舊檔案之間的差異記錄在 test.patch?
    Sol. diff /tmp/old /tmp/new > test.patch
  12. 如何以 diff 比對兩目錄 /tmp/old 與 /tmp/new,並將新舊檔案之間的差異記錄在 test.patch,且加入差異部分前後 3 行,以增加輸出之可讀性?
    Sol. diff -u /tmp/old /tmp/new > test.patch
  13. 若有一檔案 test.patch 記錄兩目錄 /tmp/old 與 /tmp/new 之間的差異,如何將舊的內容 /tmp/old 更新到新版 /tmp/new 的內容?
    Sol. patch -p1 < /tmp/test.patch
  14. 如何以『位元』為單位比對兩檔案 file1 與 file2?
    Sol. cmp file1 file2
  15. 以指令 cmp 比對兩檔案 file1 與 file2,最後出現 differ: byte 94, line 4,代表意義為何?
    Sol. 不同點在第四行,而且位元數是在第 94 個位元。
  16. 如何以指令 cmp 比對兩檔案 file1 與 file2,且列出第一個的不同點之字元?
    Sol. cmp -c file1 file2
  17. 以指令 cmp 比對兩檔案 file1 與 file2,最後出現 differ: byte 94, line 4 is 141 a 154 l,其中 a 與 l 代表意義為何?
    Sol. 不同點在檔案 file1 的第一個字元為 a,在檔案 file2 的第一個字元為 l。
  18. 以指令 cmp 比對兩檔案 file1 與 file2,最後出現 differ: byte 94, line 4 is 141 a 154 l,其中 141 代表意義為何?
    Sol. 字元 a 之八進位碼為 141。
  19. 以指令 cmp 比對兩檔案 file1 與 file2,最後出現 differ: byte 94, line 4 is 141 a 154 l,其中 154 代表意義為何?
    Sol. 字元 l 之八進位碼為 154。
  20. 指定格式方式輸出 printf '\x61\t\x6c\n',其中 \x61 代表意義為何?
    Sol. 表示輸出十六進位 61 (即八進位 141) 之 ASCII 字元,螢幕輸輸出應為 a。
  21. 指定格式方式輸出 printf '\x61\t\x6c\n',其中 \x6c 代表意義為何?
    Sol. 表示輸出十六進位 6c (即八進位 154) 之 ASCII 字元,螢幕輸輸出應為 l。
  22. 如何以指令 patch,配合 passwd 之補丁檔案 /tmp/test.patch,將 passwd 更新?
    Sol. patch passwd -i /tmp/test.patch
  23. 若以指令 patch 進行檔案更新,出現 patching file passwd,代表意義為何?
    Sol. 表示更新檔案 passwd 完成。
  24. 如何以指令 patch,配合 passwd 之補丁檔案 /tmp/test.patch,將 passwd 更新,且將更新內容另存於 passwd1?
    Sol. patch passwd /tmp/test.patch -o passwd1
  25. 若以指令 patch 進行檔案 file1 更新發生錯誤,系統會將錯誤訊息存在那個檔案?
    Sol. file1.rej
  26. 若補丁檔案在 /tmp/test.patch,如何以指令 patch 進行檔案 file1 更新,並指定錯誤輸出檔案為 error.rej?
    Sol. patch file1 /tmp/test.patch -r error.rej
  27. 若已以指令 patch 進行檔案 passwd 更新,現再次進行 patch,會出現什麼訊息?
    Sol. 詢問是否進行。
  28. 若已以指令 patch 進行檔案 passwd 更新,現再次進行 patch 會詢問是否進行,如果回應確定執行,會產生什麼結果?
    Sol. 系統會將更新後的內容另存在 passwd.orig
  29. 若已以指令 patch 進行檔案 passwd 更新,現再次進行 patch 會詢問是否進行,如果回應不繼續執行,會產生什麼結果?
    Sol. 系統會將錯誤訊息存在 passwd.rej
  30. 在目錄 /tmp 下,執行 diff -u old/ new/ > /tmp/test.patch,再進入目錄 /tmp/old,如何以 patch 進行目前目錄 /tmp/old 下所有檔案之更新?
    Sol. patch -p1 </tmp/test.patch
  31. 若補丁檔中記錄之檔案為 /u/howard/src/blurfl/blurfl.cdiff,則執行 patch -p0 </tmp/test.patch,其中 -p0 將以什麼字串取代?
    Sol. /u/howard/src/blurfl/blurfl.cdiff
  32. 若補丁檔中記錄之檔案為 /u/howard/src/blurfl/blurfl.cdiff,則執行 patch -p1 </tmp/test.patch,其中 -p1 將以什麼字串取代?
    Sol. u/howard/src/blurfl/blurfl.cdiff
  33. 若補丁檔中記錄之檔案為 /u/howard/src/blurfl/blurfl.cdiff,則執行 patch -p2 </tmp/test.patch,其中 -p2 將以什麼字串取代?
    Sol. howard/src/blurfl/blurfl.cdiff
  34. 若補丁檔中記錄之檔案為 /u/howard/src/blurfl/blurfl.cdiff,則執行 patch -p3 </tmp/test.patch,其中 -p3 將以什麼字串取代?
    Sol. u/howard/src/blurfl/blurfl.cdiff
  35. 若補丁檔中記錄之檔案為 /u/howard/src/blurfl/blurfl.cdiff,則執行 patch -p4 </tmp/test.patch,其中 -p4 將以什麼字串取代?
    Sol. blurfl/blurfl.cdiff
  36. 若補丁檔中記錄之檔案為 /u/howard/src/blurfl/blurfl.cdiff,則執行 patch -p5 </tmp/test.patch,其中 -p5 將以什麼字串取代?
    Sol. blurfl.cdiff
  37. 若產生補丁檔時,新舊檔案順序互換,例如 diff new old > test.patch,則要將舊版檔案 old 更新為 new,要如何執行 patch?
    Sol. patch -R old test.patch

  DYWANG_HOME