Patch とは
たいていのソフトウェアは、公開される前に動作テストが行なわれています。ソフトウェアによっては有志達による長時間におよぶベータテストを経てリリースされています。しかし、それでも残念ながらバグというものは見付かるものです。
ソースファイルで提供されているアプリケーションの作者は、そういうバグを取り除いた後、改めてソースファイルを公開するわけですが、このとき新しいソースファイルのアーカイブだけでなく、前バージョンとの差分ファイル (パッチファイル) もあわせて公開する場合があります。もちろんバグフィックスのときだけでなく、新機能を組み込んだなどのバージョンアップのときも同様です。
例えば、画像処理のためのソフトウェアである gimp の場合は以下のようなファイルが公開されています。
-rw-r--r-- 1 ftpuser ftpusers 3248672 Apr 3 23:46 gimp-1.0.4.tar.bz2 -rw-r--r-- 1 ftpuser ftpusers 3994617 Apr 3 23:41 gimp-1.0.4.tar.gz -rw-r--r-- 1 ftpuser ftpusers 31358 Apr 3 23:49 patch-1.0.3-1.0.4.gz
これらは、それぞれ以下のようなファイルです。
- gimp-1.0.4.tar.bz2
- バージョン 1.0.4 のアーカイブ (tar + bzip2)
- gimp-1.0.4.tar.gz
- バージョン 1.0.4 のアーカイブ (tar + gzip)
- patch-1.0.3-1.0.4.gz
- バージョン 1.0.3 から 1.0.4 への差分ファイル (パッチファイル)
gimp のバージョン 1.0.4 のソースファイルは圧縮されているとはいえ、3MB から 4MB 近くのサイズがあります。しかし、もし既にバージョン 1.0.3 のソースファイルを持っているのであれば、30KB 程度の差分ファイル (パッチファイル) だけ取得すればそれを使って自分で 1.0.4 のソースファイルにすることができるのです。
例えばこの差分ファイル (パッチファイル) の内容は、以下のようになっています。もちろんこの内容は差分ファイルの中から極一部だけを抜き出したものです。
diff -ruN gimp-1.0.3/Makefile.in gimp-1.0.4/Makefile.in --- gimp-1.0.3/Makefile.in Mon Mar 29 13:12:32 1999 +++ gimp-1.0.4/Makefile.in Sat Apr 3 22:16:10 1999 @@ -104,6 +104,7 @@ RANLIB = @RANLIB@ SENDMAIL = @SENDMAIL@ TIFF = @TIFF@ +USE_SYMBOL_UNDERSCORE = @USE_SYMBOL_UNDERSCORE@ VERSION = @VERSION@ WEBBROWSER = @WEBBROWSER@ XD = @XD@
最初の行を見ると判ると思いますが、これはコマンド diff により出力された内容です。コマンド diff とは、Linux やその他の UNIX 系システムには、たいてい標準でインストールされているコマンドであり、テキストファイルの内容を比較して異なる部分を調べ、その差分情報を出力してくれるプログラムです。
つまり、差分ファイルとは、コマンド diff が出力する差分情報を含むファイルです。そしてこの差分ファイルをパッチファイルとも言います。
コマンド patch とは、この差分ファイルから、ソースファイルの修正作業を自動的に行なってくれるコマンドです。そしてこの patch を使って行なうソースファイルの修正作業を「パッチをあてる」といいます。
もちろん、差分ファイルの内容を見ながら自分でエディタなどを使ってソースファイルを修正することも可能です。しかし、修正部分が非常に少ないのであればともかく、ある程度大きくなりますと修正するだけでかなりの時間がかかってしまいますし、それどころか間違えて修正してしまったり、何処かしら修正し忘れてしまうということがあるかもしれません。そのようなつまらないミスをしないためにも是非 patch の使い方を憶えて有効に使いましょう。
patchコマンドは、Linux や FreeBSD などの PC-UNIX 系のシステムには標準でインストールされていると思います。その他の UNIX 系システムでも最近は標準で入っていることも多いと思います。もしお使いの環境に、まだ patchコマンドがインストールされていない場合は、 インストール: patch - GNU patch - INSTALL を参考に、ぜひインストールしてみてください。
patch の使い方
patchコマンドの使い方はいたって単純です。カレントディレクトリをソースファイルのあるディレクトリに移してから以下のように patchコマンドを実行するだけです。
$ patch < 差分ファイル名
差分ファイルによっては、それ自身が圧縮されているファイルである場合があります。そのときはまず圧縮された差分ファイルを展開してから patch コマンドを実行する必要があります。もしくは以下のように gzip コマンドと patchコマンドをパイプで組み合わせて実行することも可能です。
$ gzip -dc file.gz | patch
上記は gzip 形式の場合ですが、bzip2 形式の場合でももちろん同様です。
以下は bzip2 コマンドと patchコマンドをパイプで組み合わせて実行する例です。
$ bzip2 -dc file.gz | patch
patch の便利な機能
patch コマンドには、様々な便利な機能があり、オプションで指定することでそれらを使うことができます。ここではインストール作業をする際に使うかもしれない機能を紹介します。
サブディレクトリ
patch コマンドは実行しながら修正したファイル名を表示していきます。差分ファイルの中から自動的に修正すべきファイルを見付けるのですが、もし見付からなかった場合は以下のようにパッチをあてるべきファイル名を尋ねてきます。
$ patch < PatchFile can't find file to patch at input line 4 Perhaps you should have used the -p or --strip option? The text leading up to this was: -------------------------- |diff -rNu sub.orig/FILE sub/FILE |--- sub.orig/FILE Wed Jun 30 20:58:52 1999 |+++ sub/FILE Wed Jun 30 20:59:17 1999 -------------------------- File to patch:
公開されている差分ファイルを使用してパッチをあてながらもこの現象が起こる理由には、作者のミスや patch コマンドを実行するディレクトリを間違えたなどが考えられます。そしてその他にも、サブディレクトリを持つソースファイルにパッチをあてる際、オプションを与えずに patch を実行してしまったということも考えられます。
patch コマンドのデフォルトの動作では、カレントディレクトリのファイルにパッチをあてようと試みます。例え差分ファイルの中には以下のようにパス名を含んだファイル名の記述があっても、です。
+++ sub/FILE Wed Jun 30 20:59:17 1999
しかし比較的大規模なソフトウェアは、たいていサブディレクトリを持っています。つまり実際にはサブディレクトリにあるソースファイルにパッチをあてなければいけないのに、patch はカレントディレクトリだけを探して、そして修正すべきファイルが見付からず、先のようにファイル名をユーザーに尋ねてきてしまうのです。
patch にサブディレクトリを扱わせるためにはオプション "-p NUM" を指定します。ここで "NUM" は、修正すべきファイルのパス名を推測する際に先頭から取り除くパスの数です。少々判り難いと思いますのでいくつか例を挙げます。
例えば、差分ファイルの内容から以下の名前を patch が見付けたとします。
/home/yokota/FtpEYE/ftpeye.pl
このとき、patchコマンドの実行時に引き数として "-p 1" を指定すると最初の "/" が取り除かれて、以下のパス名が修正すべきファイル名として使用されます。
home/yokota/FtpEYE/ftpeye.pl
もし patchコマンドの引き数に "-p 3" を与えたならば "/home/yokota/" が取り除かれて、以下のパス名が使用されます。
FtpEYE/ftpeye.pl
パス名を一つも取り除かせずに使用したい場合には、"-p 0" とオプションを与えます。つまり取り除くパス名の数を 0 と指定します。以前のバージョン 2.1 では、数値を与えない "-p" というオプションでも同様の動作をしましたが、現在の patch は数値を必ず与えなければいけないようになっていますので注意してください。
バックアップ
patchコマンドは、オプション "-b" を指定することでバックアップファイルを残すことができます。以前のバージョン 2.1 ではオプションを与えなくともデフォルトの動作としてバックアップファイルを残すようになっていましたが、現在の patch はこのオプションを与えないと修正前のファイルは残しません。
$ ls FILE PatchFile $ patch -b < PatchFile patching file `FILE' $ ls FILE FILE.orig PatchFile
デフォルトでは ".orig" という suffix を付けてバックアップファイルが作られます。もしこの suffix を変えたい場合はオプション "-z SUFFIX" で変更します。
$ ls FILE PatchFile $ patch -b -z .bak < PatchFile patching file `FILE' $ ls FILE FILE.bak PatchFile
patchコマンドでは、オプション "-z SUFFIX" で変更するほかに、環境変数 SIMPLE_BACKUP_SUFFIX で変更することもできます。
$ ls FILE PatchFile $ SIMPLE_BACKUP_SUFFIX=.backup patch -b < PatchFile patching file `FILE' $ ls FILE FILE.backup PatchFile
バックアップファイルを残す場合、suffix を付ける方法のほかにオプション "-B PREFIX" を与えることで prefix を付けることにより残す方法もあります。
$ ls FILE PatchFile $ patch -b -B BAK/ < PatchFile patching file `FILE' $ ls BAK FILE PatchFile $ ls BAK/ FILE
この場合、prefix の名前に "/" を付けることを忘れないでください。もし付けなかった場合、patchコマンドの動作は以下のようになります。
$ ls FILE PatchFile $ patch -b -B BAK < PatchFile patching file `FILE' $ ls BAKFILE FILE PatchFile
お喋りか寡黙か
稀に、パッチがどのようにあたっているのかをある程度詳しく表示してもらいたい場合があります。そのときはオプション "--verbose" を与えることにより patch は以下のように出力します。
$ patch --verbose < PatchFile Hmm... Looks like a unified diff to me... The text leading up to this was: -------------------------- |--- FILE.orig Tue Jun 29 21:43:35 1999 |+++ FILE Tue Jun 29 21:43:43 1999 -------------------------- Patching file `FILE' using Plan A... Hunk #1 succeeded at 1. done
この動作はバージョン 2.1 まではデフォルトの動作でした。今の patch コマンドでは "patching file `FILE'" と表示されるだけです。しかし、それだけの出力であっても時には煩わしい場合があります。そのようなときはオプション "-s" を付けて実行します。これで patch はエラーメッセージでないかぎり一切表示しなくなります。オプション "--quiet" と "--silent" も "-s" と同様の働きをします。
patch が失敗したとき
公開されている差分ファイルを使用していても、稀にパッチをあてることに失敗することがあります。正直、公開されている差分ファイルを使っているのに patch がエラーになると、ちょっと焦ってしまうと思います。しかし、エラーとなってしまった場合はユーザーが自分自身で何らかの対応をしなければいけません。
まずはどのような場合に patch コマンドが失敗をするのか、その主な理由を以下に挙げます。
- 既にローカルでソースファイルに修正していた
- タブやスペースが違う
- バージョンが合わない
- 漢字コードが違う
まず考えられるのが、例えば自分で何らかのバグをフィックスしていたときや、機能を追加していたなどでローカルにソースファイルを修正していた場合です。バグフィックスならば、その修正方法が差分ファイルと同じであればそのままでも構わないかもしれませんが、違っている場合は自分で修正しておく必要があります。
patch は、修正すべき箇所を見付けられなかったときなど、パッチをあてることに失敗した場合は、その失敗した部分の差分情報を "reject file" に出力します。reject file は、通常は修正するファイルに ".rej" という suffix を付けた名前になります。したがって以下のように find コマンドを実行することで reject file を検索することができます。
$ find . -type f -name '*.rej' -print
差分ファイルの内容は一見うまくあたりそうなのに失敗してしまう場合は、いつのまにかタブがスペースに変換されていたり、その逆にスペースがタブに変換されていたりする場合があります。その場合はタブやスペースの違いは無視するオプション "-l" を使用してパッチをあてることで解決できます。
差分ファイルが複数ある場合など、途中の差分ファイルを抜かしたり、順番を間違えてパッチをあてたりした場合も当然ながら失敗してしまいます。必要なパッチが複数ある場合は必ずその順番通りに、間を抜かさずにパッチをあてるようにしなければいけません。
最後に、これは日本語を含むソースファイルなどの場合ですが、その漢字コードが一致していないために失敗する場合があります。その場合は漢字コードを必ず一致させるように変換してからパッチをあてるようにしてください。
差分ファイルのフォーマット
差分ファイル (パッチファイル) となる diff コマンドの出力にはいくつかの種類があります。ここではそれらについて簡単に解説をします。
まずはオプションを与えずに diff コマンドを実行した場合のフォーマットです。
$ diff hello.c.orig hello.c
5c5
< printf ("Hello World\n");
---
> printf ("Hello World!!\n");
二つのファイルを較べて違いがある行が表示されます。このときの "<" で始まる行は古いファイルでの内容で、">" で始まる行は新しいファイルの内容です。
diff コマンドにオプション "-e" を与えた場合のフォーマットは以下のようになります。
$ diff -e hello.c.orig hello.c
5c
printf ("Hello World!!\n");
.
これはコマンド ed を使用して修正することができるスクリプトになっています。
上記二つの例をよく見ると判ると思いますが、「五行目を修正する」というように行番号に依存したフォーマットになっています。これではオリジナルに完全に一致していないと当然ながら patch は正常な修正作業ができなくなってしまいます。
そのため、通常公開されている差分フィイルでは、変更された行だけでなくその前後の行も一緒に出力してくれる context と呼ばれるフォーマットが使われています。このフォーマットで出力するには、diff コマンドにオプション "-c" を使用して実行します。
$ diff -c hello.c.orig hello.c
*** hello.c.orig Fri Jul 2 11:49:49 1999
--- hello.c Fri Jul 2 12:50:54 1999
***************
*** 2,7 ****
int main (int argc, char *argv[])
{
! printf ("Hello World\n");
return (0);
}
--- 2,7 ----
int main (int argc, char *argv[])
{
! printf ("Hello World!!\n");
return (0);
}
上に変更前、下に変更後の内容が表示されます。このフォーマットですと、例えば手元のソースファイルを修正していたとしても、それがオリジナルと行番号が少々違う程度でしたら、コマンド patch は前後関係から変更すべき箇所を見付け出して修正してくれます。
GNU の diff などでは、同じように前後の行を出力してくれるフォーマットに、unified と呼ばれるフォーマットがあります。このフォーマットで出力するには diff コマンドにオプション "-u" を使用します。
$ diff -u hello.c.orig hello.c
--- hello.c.orig Fri Jul 2 11:49:49 1999
+++ hello.c Fri Jul 2 12:50:54 1999
@@ -2,6 +2,6 @@
int main (int argc, char *argv[])
{
- printf ("Hello World\n");
+ printf ("Hello World!!\n");
return (0);
}
パッチファイルとは、これら四つの形式の差分情報を含んでいるファイルです。
最後になりましたが、 diff コマンドを使用した差分ファイルの作成では、較べるファイル名の指定は必ず以下のようにする必要があります。
$ diff 古いファイル 新しいファイル
特に、その差分情報を patch で使用する場合などは、もし「古いファイル」と「新しいファイル」の順番を逆にしてしまうと差分情報も逆の、いわゆるリバースパッチになってしまいますので注意してください。
patch の実用
パッチをあてることにより、ソースファイルをバージョンアップさせることはできても、そのソースファイルで実際にコンパイル作業をすると、ちょっと困ったことが起きる場合があります。
問題点は「パッチをあてて修正してできたファイルの時刻」です。詳しく解説するために、ここで実際に patch を使ってソフトウェアのソースファイルをバージョンアップし、コンパイル作業をしてみましょう。例に用いるのは以下のソースファイルのアーカイブと差分ファイルです。
$ ls -l total 778 -r--r--r-- 1 yokota install 158833 Jul 31 1998 make-3.76.1-3.77.diff.gz -r--r--r-- 1 yokota install 631458 Sep 19 1997 make-3.76.1.tar.gz
見て判るように GNU make バージョン 3.76.1 と、バージョンを 3.76.1 から 3.77 へ上げるための差分ファイルです。まずは GNU make 3.76.1 のアーカイブを展開し、ディレクトリを移ります。
$ tar xfz make-3.76.1.tar.gz $ cd make-3.76.1
ここで、ソースファイルをバージョン 3.76.1 から 3.77 に上げるためにパッチをあてます。その後に、一応 reject file ができていないかを確認します。
$ gzip -dc ../make-3.76.1-3.77.diff.gz | patch -p1 -s $ find . -type f -name '*.rej' -print
reject file は見付かりませんでしたので、全て正常にパッチをあてることができました。つまりこれでソースファイルはバージョン 3.77 になりました。では configure を実行して Makefile を作成し、make をしてみましょう。
$ ./configure creating cache ./config.cache checking for a BSD compatible install... /usr/bin/ginstall -c checking whether build environment is sane... yes ... updating cache .././config.cache creating ./config.status creating Makefile creating config.h $ make cd . && aclocal cd . && automake --gnu --include-deps Makefile ...
ここでは configure の出力は長いので途中は省いています。
make を実行したときに "aclocal" および "automake" が実行がされてしまっています。Autoconf と Automake という GNU のソフトウェアを事前にインストールしてある環境であれば、この後も問題なくコンパイルを続けることができかもしれません。しかしこの二つのコマンドの実行は、本来行なう必要がないものなのです。実際、バージョン 3.77 のアーカイブからコンパイル作業をするときには、この実行は行なわれません。
何故このような違いが起こってしまうのか、その原因を詳しく解説するために、以下に主な原因となっているファイルのリストを full time とともに挙げます。
$ ls -lt --full-time ac* config.h.in configure configure.in \
Makefile.am Makefile.in stamp-h.in
-rwxr-xr-x 1 yokota users 130362 Sat Jul 3 19:35:23 1999 configure
-rw-r--r-- 1 yokota users 5008 Sat Jul 3 19:35:23 1999 configure.in
-rw-r--r-- 1 yokota users 4633 Sat Jul 3 19:35:22 1999 acinclude.m4
-rw-r--r-- 1 yokota users 9329 Sat Jul 3 19:35:22 1999 aclocal.m4
-rw-r--r-- 1 yokota users 7829 Sat Jul 3 19:35:22 1999 config.h.in
-rw-r--r-- 1 yokota users 4223 Sat Jul 3 19:35:22 1999 Makefile.am
-rw-r--r-- 1 yokota users 22736 Sat Jul 3 19:35:22 1999 Makefile.in
-rw-r--r-- 1 yokota install 10 Sat Sep 20 03:37:12 1997 stamp-h.in
-rw-r--r-- 1 yokota install 595 Tue Aug 19 03:11:12 1997 acconfig.h
そして Makefile から、これらのファイルの依存関係を示している部分を以下に挙げます。
$(srcdir)/Makefile.in: Makefile.am $(top_srcdir)/configure.in $(ACLOCAL_M4)
cd $(top_srcdir) && $(AUTOMAKE) --gnu --include-deps Makefile
...
$(ACLOCAL_M4): configure.in acinclude.m4
cd $(srcdir) && $(ACLOCAL)
...
$(srcdir)/configure: $(srcdir)/configure.in $(ACLOCAL_M4) $(CONFIGURE_DEPENDENCIES)
cd $(srcdir) && $(AUTOCONF)
...
$(srcdir)/config.h.in: $(srcdir)/stamp-h.in
$(srcdir)/stamp-h.in: $(top_srcdir)/configure.in $(ACLOCAL_M4) acconfig.h
cd $(top_srcdir) && $(AUTOHEADER)
@echo timestamp > $(srcdir)/stamp-h.in
これらの情報から、"aclocal" が実行されてしまう原因は、aclocal.m4 より configure.in と acinclude.m4 が新しい時刻になってしまったことであり、"automake" が実行されてしまう原因は、Makefile.in よりも configure.in と aclocal.m4 が新しい時刻になってしまったことであることが判ります。
これはパッチをあてたことによりファイルの時刻まで更新されてしまったために起こった問題です。上記の configure.in などの時刻はまさにパッチをあてた時刻なのです。
最近の patch は、このような問題を回避する方法が用意されています。ファイルを修正しながらも、そのファイルの時刻を差分ファイルに記述されている時刻に合わせるのです。ただし時刻には TimeZone という概念がありますので、少々コツがいります。
これも実際に行ないながら解説していきます。まず先程展開していたソースファイルは消去し、あらためてアーカイブを展開します。
$ tar xfz make-3.76.1.tar.gz $ cd make-3.76.1
次に、差分ファイルから先頭の数行だけを抜き出して表示します。
$ gzip -dc ../make-3.76.1-3.77.diff.gz | head -4 diff -uPr make-3.76.1/AUTHORS make-3.77/AUTHORS --- make-3.76.1/AUTHORS Wed Aug 27 16:30:54 1997 +++ make-3.77/AUTHORS Fri Jul 17 09:23:04 1998 @@ -1,28 +1,29 @@ Broken pipe
これで、差分ファイルを作成した人が持っている AUTHORS というファイルの時刻を知ることができます。上記の例ではバージョン 3.76.1 では "Aug 27 16:30:54 1997" であり、バージョン 3.77 では "Jul 17 09:23:04 1998" ということが判ります。では自分で持っている AUTHORS というファイルの時刻を確認してみましょう。
$ ls -l --full-time AUTHORS -rw-r--r-- 1 yokota install 1391 Thu Aug 28 05:30:54 1997 AUTHORS
自分で持っているのはバージョン 3.76.1 なのですから、"Aug 27 16:30:54 1997" になっていて欲しいのですが、残念ながら "Aug 28 05:30:54 1997" であり 13 時間進んでいることが判ります。この差は、日本では GMT+9、つまり GMT (グリニッジ標準時) より日本時間は 9 時間進んでいることと、差分ファイルを作成した作者は GMT より 4 時間遅れている地域であるためです。したがって作者と同じく GMT より 4 時間遅れた TimeZone を指定して、再びファイルの時刻を確認してみます。
$ env TZ=GMT-4 ls -l --full-time AUTHORS -rw-r--r-- 1 yokota install 1391 Wed Aug 27 16:30:54 1997 AUTHORS
これで作者の時刻と同じになることが確認できました。あとは patch に、AUTHORS というファイルを修正しつつも、ファイルの時刻は作者と同じ "Jul 17 09:23:04 1998" にしてくれるように指定します。もちろん AUTHORS だけでなく、パッチをあてる全てのファイルも同様です。このためにオプション "-T" と "-f" を使用します。
$ gzip -dc ../make-3.76.1-3.77.diff.gz | env TZ=GMT-4 patch -p1 -s -f -T $ env TZ=GMT-4 ls -l --full-time AUTHORS -rw-r--r-- 1 yokota users 1368 Fri Jul 17 09:23:04 1998 AUTHORS
オプション "-T" は修正したファイルの時刻を、差分ファイルより取得した時刻で設定するオプションです。しかし、このオプションは修正前のファイルの時刻も一致している場合のみ有効なオプションです。したがって何らかの理由により修正前のファイルの時刻が作者のものと一致していない場合は設定されません。そのような場合は、オプション "-f" もあわせて使用することで強制的に時刻を設定することができます。
ただし、オプション "-f" は少々危険なオプションであることを憶えておいてください。このオプションを指定しますと、patch は、ユーザーが今何をしようとしているのかを正確に把握しているものとみなします。つまりパッチをあてるべきファイルが見付からない場合は黙ってスキップしますし、差分ファイルが実はリバースパッチであったとしても何も言わずに修正を行なってしまいます。このあたりの問題はない差分ファイルであることを確認した上で使用するようにしてください。
さて、これでパッチをあてつつもソースファイルは作者と同じ時刻になりましたが、実はもう一つ問題があります。"stamp-h.in" の時刻です。上記の Makefile の内容から、このファイルの中身は "timestamp" という文字だけであることが判ると思います。これはバージョンが上がっても変わることはありません。つまりこのファイルは「タイムスタンプ」という意味でのみ必要なもので、内容は全く意味がないのです。そのためこのファイルは差分ファイルには含まれません。内容が変わらないのですから当然ですね。
しかし上記の Makefile の内容から、この "stamp-h.in" というファイルが古いと autoheader を実行してしまうことになってしまいます。したがって "stamp-h.in" というファイルだけはユーザーが新しい時刻にしておく必要があります。
$ touch stamp-h.in
以上で件の問題は回避でき、問題なくコンパイルを行なうことができます。