Linux

【Linuxコマンド】パッチを使いこなしたい

【※ 当記事は2020年7月2日時点の情報です】

ペイヴメント(@pavement1234)です。

エンジニア
エンジニア

Linuxのパッチの作り方、パッチの当て方を知りたい

こんな悩みを解決します。

パッチを作る方法

Linuxエンジニアらしいパッチのつくりかたを読みました。

コマンド実行例

1つのソースコードに対する差分ファイルを作成する
diff -up [オリジナルソース] [修正後ソース] > 差分ファイル

ディレクトリ以下のソースコードに対する差分ファイルを作成する
diff -uprN [オリジナルソースディレクトリ] [修正後ソースディレクトリ] > 差分ファイル

Linuxカーネルのドキュメントフォルダ(Documentation/)にSubmittingPatchesというドキュメントがあり、このドキュメントの1章にUse “diff -up” or “diff -uprN” to create patches.  git generates patches in this form by default; if you’re using git, you can skip this section entirely.と書いてある様子。

オプション:意味

u:unified形式で出力する
p:変更箇所の関数名(C言語)を表示する
r:サブディレクトリを再帰的に比較する
N:比較するファイルが無い場合、同名の空ファイルがあるのと同じ動作をする

差分ファイルを作成してみる

修正前のフォルダはこんな感じ。

before
├a
│└a.txt
├b
│└b.txt
└c
└c.txt

修正後のフォルダはこんな感じ。青字が変更箇所。

after
├a
│└a.txt 修正
├bbb リネーム
│└bbb.txt リネーム&修正
├c 配下のファイルを削除
└d 新規
└d.txt 新規

差分ファイルを作成。

$ diff -uprN before after > test.patch
$ vi test.patch
diff -uprN before/a/a.txt after/a/a.txt
— before/a/a.txt 2019-07-21 23:09:28.089420189 +0900
+++ after/a/a.txt 2019-07-21 23:10:30.037418387 +0900
@@ -1,2 +1,4 @@
+12345678901234567890
1234567890

+
diff -uprN before/b/b.txt after/b/b.txt
— before/b/b.txt 2019-07-21 23:09:40.381419832 +0900
+++ after/b/b.txt 2019-07-21 23:10:54.201417684 +0900
@@ -1 +1,2 @@
-abcdefg
+abcdefghijklmnop
+
diff -uprN before/c/c.txt after/c/c.txt
— before/c/c.txt 2019-07-21 23:09:52.717419473 +0900
+++ after/c/c.txt 1970-01-01 09:00:00.000000000 +0900
@@ -1,2 +0,0 @@
-あいうえお

diff -uprN before/d/d.txt after/d/d.txt
— before/d/d.txt 1970-01-01 09:00:00.000000000 +0900
+++ after/d/d.txt 2019-07-21 23:11:20.217416927 +0900
@@ -0,0 +1,3 @@
+あいうえお
+かきくけこ
+

差分ファイル(パッチ)を適用する

【 patch 】コマンド――テキストファイルに差分を適用する(基本編)を読んでみました。

コマンド実行例

1つのファイルに対する差分ファイル適用
patch 元のファイル 差分ファイル

-u、-c形式で作成した(ファイル名情報を持つ)差分ファイルからパッチを当てる。
-p数字については次に詳しく説明するが、数字の数だけディレクトリ名を無視できるらしい。
patch -p数字 < 差分ファイル

①まず-p0で失敗

先ほどdiff -uprN before after > test.patchで作成した差分ファイルは、こんな配置になっていました。

$ ls
after before test.patch

このディレクトリでパッチを適用してみました。-p0はディレクトリ名を無視せずそのまま使うことを意味します。私はbeforeにパッチが当たりafterと同じ内容になるという誤解をしていました。結果は失敗。

$ patch -p0 < test.patch
patching file after/a/a.txt
Hunk #1 succeeded at 2 (offset 1 line).
patching file after/b/b.txt
Reversed (or previously applied) patch detected!  Assume -R? [n] Apply anyway? [n] Skipping patch.
1 out of 1 hunk ignored — saving rejects to file after/b/b.txt.rej
patching file before/c/c.txt
The next patch would create the file after/d/d.txt,
which already exists!  Assume -R? [n] Apply anyway? [n] Skipping patch.
1 out of 1 hunk ignored

まず、パッチ適用後のファイルafter/a/a.txtに対して更にパッチが当たってしまい、12345678901234567890がもう1行増えてしまいました。

$ vi after/a/a.txt

12345678901234567890
12345678901234567890
1234567890

次にafter/b/b.txtへのパッチ適用はReversed (or previously applied) patch detected!  というエラーが検出されパッチ適用がスキップされました。(要はafter/b/b.txtがパッチ適用後の内容と一致したためpatchコマンドがオカシイと気づいてくれました)

次にafter/c/c.txtが無いためbefore/c/c.txtにパッチが適用されたがディレクトリcもろとも消えました。

最後にafter/d/d.txtはパッチ適用前は存在しないハズなのに存在してるからスキップされました。

悲惨な状況です。こんなのが開発プロジェクトで発生したら恐怖ですがこれはプライベートなので何度失敗しても良いのです。

②失敗原因を考えてみた

①の失敗原因を分析した。【原因1】パッチ適用先のディレクトリが2つあったこと、【原因2】パッチ適用対象のディレクトリは昇順に検索されること。の2つが考えられます。

まず【原因1】の検証。afterを削除してパッチ適用してみました

【結果】パッチ適用が成功。

$ ls
after before test.patch
$ rm -r after
$ ls
before test.patch
$ patch -p0 < test.patch
patching file before/a/a.txt
patching file before/b/b.txt
patching file before/c/c.txt
patching file before/d/d.txt

次に【原因2】の検証。afterをbに変更。beforeをaに変更してパッチ適用してみました。

【結果】パッチ適用が失敗。ディレクトリ名を変えてしまったのでファイルが見つからなくなったみたいです。-p0を指定する限りディレクトリ名は変えてはならぬということですね。

$ ls
after before test.patch
$ mv after b
$ mv before a
$ ls
a b test.patch
$ patch -p0 < 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 -uprN before/a/a.txt after/a/a.txt
|— before/a/a.txt 2019-07-21 23:09:28.089420189 +0900
|+++ after/a/a.txt 2019-07-21 23:10:30.037418387 +0900
————————–
File to patch:
Skip this patch? [y] Skipping patch.
1 out of 1 hunk ignored
can’t find file to patch at input line 12
Perhaps you used the wrong -p or –strip option?
The text leading up to this was:
————————–
|diff -uprN before/b/b.txt after/b/b.txt
|— before/b/b.txt 2019-07-21 23:09:40.381419832 +0900
|+++ after/b/b.txt 2019-07-21 23:10:54.201417684 +0900
————————–
File to patch:
Skip this patch? [y] Skipping patch.
1 out of 1 hunk ignored
The next patch would delete the file after/c/c.txt,
which does not exist! Assume -R? [n] Apply anyway? [n] Skipping patch.
1 out of 1 hunk ignored
patching file after/d/d.txt

③というわけで-p1を試してみましたが、1回目は失敗。

まぁ私は要領が悪いので良く失敗します。以下のようにパッチを当てようとしたら失敗。今回の失敗要因は簡単。-p1を指定したので差分ファイル内に記載されたディレクトリ階層の1階層目であるafter、beforeが削除されました。要は「after/a/a.txt」が「a/a.txt」となったわけですがpatchコマンドを実行したディレクトリには、a、b、cのディレクトリがないため、結果ファイルが見つからないというわけです。

$ ls
after before test.patch
$ rm -r after
$ mv before zzz
$ ls
zzz test.patch
$ patch -p1 < 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 -uprN before/a/a.txt after/a/a.txt
|— before/a/a.txt 2019-07-21 23:09:28.089420189 +0900
|+++ after/a/a.txt 2019-07-21 23:10:30.037418387 +0900
————————–
File to patch:
Skip this patch? [y] Skipping patch.
1 out of 1 hunk ignored
can’t find file to patch at input line 12
Perhaps you used the wrong -p or –strip option?
The text leading up to this was:
————————–
|diff -uprN before/b/b.txt after/b/b.txt
|— before/b/b.txt 2019-07-21 23:09:40.381419832 +0900
|+++ after/b/b.txt 2019-07-21 23:10:54.201417684 +0900
————————–
File to patch:
Skip this patch? [y] Skipping patch.
1 out of 1 hunk ignored
The next patch would delete the file c/c.txt,
which does not exist! Assume -R? [n] Apply anyway? [n] Skipping patch.
1 out of 1 hunk ignored
patching file d/d.txt

④というわけで-p1でパッチ適用を成功させるためにはこう打てばよい

フォルダ名を変えたりする小細工は不要。パッチを適用したいbeforeにディレクトリ移動(cd)し、1つ上の階層のパッチファイルを指定すればよいです。ここまで来るのにちょっと苦労しましたが、パッチの仕組みについて良く分かったので良しとします。

$ ls
after before test.patch
$ cd before
$ patch -p1 < ../test.patch
patching file a/a.txt
patching file b/b.txt
patching file c/c.txt
patching file d/c.txt

まとめ

これまで、パッチ適用の仕組みがイマイチ分かってなくて、誰かが作った手順書通りにやってましたが、これからは迷いなくパッチ適用が出来そうです。良かった良かった。

ABOUT ME
ペイヴメント
ペイヴメントのエンジニア塾(当ブログ)では20年以上の経験から得られたプログラミング系ノウハウについてベテランにも満足して頂けるような内容の濃いコンテンツを初心者にも分かりやすい形で日々発信しています。【経歴】ベンチャーのソフトハウスで4年勤務後、精密機器メーカーのソフト開発部門に勤務し今に至ります。