連載
» 2017年05月12日 05時00分 UPDATE

LinuxコマンドTips(109):【patch】コマンド――テキストファイルに差分を適用する(応用編その2)

本連載は、Linuxのコマンドについて、基本書式からオプション、具体的な実行例までを紹介していきます。今回は、テキストファイルに差分を適用する「patch」コマンドです。

[西村めぐみ,@IT]
「Linux基本コマンドTips」のインデックス

連載目次

 本連載では、Linuxの基本的なコマンドについて、基本的な書式からオプション、具体的な実行例までを分かりやすく紹介していきます。今回はテキストファイルに差分を適用する「patch」コマンドです。

patchコマンドとは?

 「patch」はテキストファイルに差分を適用するコマンドです。古いファイルと差分ファイルを基に、新しいファイルを作成します。差分ファイルは「diff」コマンド(第102回105回)を使って作成します。

 差分ファイルを“パッチファイル”、差分を適用することを“パッチを当てる”というように表現することがあります。



patchコマンドの書式

patch [オプション] 元のファイル 差分ファイル

patch -p 数字 < 差分ファイル

※[ ]は省略可能な引数を示しています





patchコマンドの主なオプション

 patchコマンドのオプションを4種類に分けて紹介します。最初は差分ファイル関係の主なオプションです。

短いオプション 長いオプション 意味
-i ファイル名 --input=ファイル名 差分を指定したファイルから読み取る(デフォルトは標準入力)
-n --normal 差分をノーマルdiff(diffのデフォルト出力による差分)として解釈する
-c --context 差分をコンテキストdiff(diff -cで出力した差分)として解釈する
-u --unified 差分をunified形式のコンテキストdiff(diff -uで出力した差分)として解釈する
-F 行数 --fuzz=行数 コンテキストdiffに対し、適用する位置を探す際に無視できる行数を指定。デフォルトは2。diff -cで出力した際の行数(デフォルトは3)より大きな数を指定しないよう注意。数字が大きいと間違った場所に適用される場合が多くなる
--binary 全てのファイルをバイナリモードで読み書きする(diff -a --binaryで作成した差分を対象とする)
-e --ed 差分をedスクリプトとして解釈する
-D 名前 --ifdef=名前 差分を#ifdef "名前"〜#endif形式で適用する

 patchコマンドの差分適用時の処理に関係したオプションは次の通りです。

短いオプション 長いオプション 意味
-d ディレクトリ --directory=ディレクトリ 指定したディレクトリへ移動してから他の処理を行う
-p個数 --strip=個数 差分に記載されたファイル名から指定した個数分のパス指定を取り除く(本文参照)
-R --reverse 新旧のファイルが反転していると見なす
-N --forward 反転していると思われる差分や適用済みと思われる差分を無視する
-l --ignore-whitespace 空白の個数の違いや行末の空白の有無による違いを無視する
-E --remove-empty-files 差分を適用した結果空になったファイルを削除する
-o ファイル名 --output=ファイル名 指定した名前のファイルに出力する(デフォルトは同名のファイルで置き換える:本文参照)
-r ファイル名 --reject-file=ファイル名 適用できなかった差分(reject)を出力するファイル名。指定していない場合は「元のファイル名.rej」に出力
-Z --set-utc 差分の適用で内容が書き換わった場合、適用後のファイルの変更日時とアクセス日時をコンテキスト差分ファイルのヘッダに書かれているタイムスタンプに設定する(時刻はUTCであるものと見なす)
-T --set-time 差分の適用で内容が書き換わった場合、適用後のファイルの変更日時とアクセス日時をコンテキスト差分ファイルのヘッダに書かれているタイムスタンプに設定する(ヘッダはローカルの時刻を使っていると見なすため非推奨)
--quoting-style=スタイル ファイル名を出力するスタイルを以下から指定。デフォルトは環境変数 QUOTING_STYLEで指定可能、指定がない場合はshellとなる

 「literal」 ファイル名をそのまま出力
 「shell」 必要に応じシェル用の引用符を付けて出力
 「shell-always」 常にシェル用の引用符を付けて出力
 「c」 C言語文字列と同様な引用符を付けて出力
 「escape」 「c」同様に引用符を付けるが、最初と最後の二重引用符は省略する

 patchコマンドのバックアップに関係する主なオプションは次の通りです。

短いオプション 長いオプション 意味
-b --backup バックアップファイルを作成する(バックアップファイル名の決め方は-Vオプションで指定)
--backup-if-mismatch 適用できなかった差分があり、かつ、バックアップが指定されていなかった場合にファイルをバックアップする(POSIXに準拠していない場合のデフォルト)
--no-backup-if-mismatch 適用できなかった差分があり、かつ、バックアップが指定されていなかったらファイルをバックアップしない(POSIX準拠時のデフォルト)
-V 命名法 --version-control=命名法 バックアップファイルの名前を決定する方法を以下から指定する。デフォルトは環境変数PATCH_VERSION_CONTROLまたはVERSION_CONTROLで指定可能、指定がない場合はexistingとなる

 「existing または nil」 番号付きのバックアップがある場合はバックアップに番号を付けるが、ない場合は簡易バックアップを作る
 「numbered または t」 バックアップに番号を付ける(file.txtならばfile.txt.~1~のようになる)
 「simple または never」 簡易バックアップ(ファイル名は-B,-Y,-zのいずれかで指定、指定がない場合は環境変数SIMPLE_BACKUP_SUFFIX、いずれも指定していない場合はファイル名の末尾に.origを付ける)
-B 文字列 --prefix=文字列 簡易バックアップファイル名で、元のファイル名の前に文字列を付ける(「-B /junk/」と指定すると、「src/file.c」のバックアップは「/junk/src/file.c」となる)
-Y 文字列 --basename-prefix=文字列 簡易バックアップファイル名で、ファイル名のベース名に文字列を付ける(「-Y .del/」と指定すると、「src/file.c」のバックアップは「src/.del/file.c」となる)
-z 文字列 --suffix=文字列 簡易バックアップファイル名で、元のファイルの末尾に文字列を付ける(「-z ~」と指定すると、「src/file.c」のバックアップは「src/file.c~」となる。環境変数SIMPLE_BACKUP_SUFFIXでも指定可能)

 patchコマンドの全体的な動作に関係する主なオプションは次の通りです。

短いオプション 長いオプション 意味
--dry-run 差分を当てた場合の結果を表示する(実際にはファイルの変更を行わない)
-s --silent/--quiet エラーメッセージ以外は出力しない
--verbose 処理中の情報を出力する
-t --batch ユーザーに問い合わせずに処理を行う

・ヘッダにファイル名を含まない差分ファイルはスキップする(-fと同じ)
・ファイルのバージョンが差分中の Prereq:の行に書かれたバージョンと違っていても差分を適用し、差分が反転しているように見える場合は反転していると見なす
-f --force ユーザーに問い合わせずに処理を行う

・ヘッダにファイル名を含まない差分ファイルはスキップする(-tと同じ)
・ファイルのバージョンが差分中の Prereq:の行に書かれたバージョンと違っていても差分を適用し、差分が反転しているように見えても反転していないと見なす
・(-T/-Z指定時)ファイルの内容が書き換わらない場合もタイムスタンプを強制的に変更する
--posix POSIX標準に従う(差分を適用した結果空になったファイルを削除しない、適用できなかった差分があった場合もファイルをバックアップしないなど)


ケーススタディー1:同じパッチを2回適用した場合

 第108回では、rsync 3.1.1のソースと、1つ後のバージョンとの差分である「rsync-3.1.1-3.1.2.diffs.gz」を使って「複数のファイルに対してまとめてパッチを当てる」という操作を試しました。今回も同じファイルを題材にします。パッチを2回適用してしまった場合と、別の修正を施した後にパッチを適用する場合について主に取り上げます。

 公式サイトからダウンロードしたファイルは以下の通りです。第108回と同じファイルを使いました。

 第108回の手順でrsync-3.1.1へのパッチ当てを試した方は、いったんrsync-3.1.1ディレクトリを削除して、あらためてrsync-3.1.1.tar.gzを展開してください(画面1)。

画面1 画面1 ケーススタディーの準備のため初期状態に戻す

 では始めましょう。3.1.1のソースに対してパッチを当てるので、3.1.1のディレクトリ(rsync-3.1.1)へ移動してからpatchコマンドを実行します。

 rsync-3.1.1-3.1.2.diffsの場合、途中で1つファイル名が解決できず「File to patch:」というメッセージが表示されました(画面2)。ここではファイル名を入力せず[Ctrl]+[C]を用いて中断しました。その後、patchコマンドを再実行した場合の動作を見てみましょう。

コマンド実行例

patch -p1 < パッチファイル名

(ファイルに差分を適用する)


画面2 画面2 [Ctrl]+[C]を入力してコマンドの動作を中断したところ

 パッチを適用した後のファイルに同じパッチを適用しようとした場合、パッチ適用前の状態に戻すかどうかを確認するメッセージが表示されます。変更を元に戻すようなパッチを「リバースパッチ」と呼ぶことがあり、patchコマンドでは「-R」オプションで実行します。

 今回、既にパッチが適用されたファイルがあるため、途中で「Assume -R? [n]」というメッセージが表示されます(画面3)。「-R」オプションをこのファイルにのみ適用するかどうかという意味です。今回は元に戻しませんから[n]を入力して[Enter]キーを押します。さらに「Apply anyway? [n] 」という確認メッセージが表示されるので[n]で先に進みます。これ以降はこのようなメッセージが表示されなくなります。

 「〜? [n]」のように表示されているので、デフォルト入力は「[n]」です。[n]の入力を省略して[Enter]キーを押しても構いません。

画面3 画面3 [n]を入力して処理を進めるところ

 今回の例では「File to patch:」と表示されますから、「aclocal.m4」と入力して先に進めてください。

 全て適用が終わったら、前回同様、動作確認用にダウンロードして展開してあったrsync-3.1.2と比較してみましょう。画面のように「diff -qr rsync-3.1.1 rsync-3.1.2」としてディレクトリ同士を比較しました。違いがない場合、何もメッセージは表示されません(画面4)。

 「-q」は違いがあった場合、「ファイル1とファイル2は異なります」というように簡潔に表示するオプション、「-r」はサブディレクトリを再帰的に処理するオプションです。

画面4 画面4 patchコマンドの終了後、diffコマンドで違いを確認した


ケーススタディー2:別の修正が入っていた場合

 元のファイルに対し、パッチを作成した環境とは異なる修正が入っていた場合、何が起こるのか試してみましょう。

 いったんrsync-3.1.1のソースを元の状態に戻します。rsync-3.1.1ディレクトリを削除して、あらためてrsync-3.1.1.tar.gzを展開してください(画面5)。

 続いて、rsync-3.1.1の一部を書き換えます。ここでは、3.1.1から3.1.2で変更されていないREADMEと、変更されているMakefile.inに対し、それぞれ1行追加しています。

画面5 画面5 rsync-3.1.1を元の状態に戻し、ファイルに修正を加える

 それぞれ、画面6画面7のように修正を施しました。

画面6 画面6 rsync-3.1.1/READMEの変更内容
画面7 画面7 rsync-3.1.1/Makefile.inの変更内容

 変更後のrsync-3.1.1を、rsync-3.1.2とdiffコマンドで比較しました(画面8)。Makefile.inは3.1.2での変更箇所が多数あるためmoreコマンドで表示しています(画面9)。

画面8 画面8 diffコマンドによる比較結果(README)
画面9 画面9 diffコマンドによる比較結果(Makefile.in)

 それでは、patchコマンドで差分(rsync-3.1.1-3.1.2.diffs)を適用してみましょう。これまでと同じように、rsync-3.1.1ディレクトリに移動し、「patch -p1 < ../rsync-3.1.1-3.1.2.diffs」を実行します(画面10)。途中でファイル名として「aclocal.m4」を入力してください。

画面10 画面10 patchでrsync-3.1.1-3.1.2.diffsを適用したところ

 実行が終わったら先ほど同様、rsync-3.1.2と比較します。今回は「diff -ru rsync-3.1.1 rsync-3.1.2」として、unified形式(-u)で出力しています(画面11)。

 画面4とは違い、手入力で追加した「動作確認のため追加」という行だけが表示され、その他は全てのパッチが適用されていることが分かります。

画面11 画面11 パッチ適用後のディレクトリを比較したところ


ケーススタディー3:変更がバッティングした場合

 自分で修正した箇所と、パッチで変更される箇所が重なる場合に何が起こるのかも見ておきましょう。

 先ほどMakefile.inに追加した行は、公式で3.1.1から3.1.2へと修正されていた箇所とは異なる場所でした。このため問題なくパッチを適用することができました。

 次は14行目付近を変更後(画面12)、patchコマンドで差分(rsync-3.1.1-3.1.2.diffs)を適用します。

画面12 画面12 Makefile.inの変更箇所

 適用後の様子をdiffで確認すると、「rsync-3.1.1/Makefile.in.orig」と「rsync-3.1.1/Makefile.in.rej」というファイルが作成されたことが分かります(画面13)。

 「〜.orig」はpatchコマンドで書き換える前のファイル(original)で、「〜.rej」は適用できなかった箇所(reject)の差分です。rejファイルの内容を確認すると、14行目付近のパッチがうまく適用できなかったことが分かります(画面14)。実際に使用しているデータであれば、これらのファイルを見て、「どちらの修正を生かすか?」あるいは「両方の修正を生かすことは可能か?」などと考えなければなりません。

画面13 画面13 パッチ適用後の違いを表示した
画面14 画面14 rejファイルの内容を表示した


筆者紹介

西村 めぐみ(にしむら めぐみ)

PC-9801NからのDOSユーザー(LinuxはPC-486DXから)。1992年より生産管理のパッケージソフトウェアの開発およびサポート業務を担当。のち退社し、ライターとして活動。著書に『図解でわかるLinux』『らぶらぶLinuxシリーズ』『はじめてでもわかるSQLとデータ設計』『シェルの基本テクニック』など。2011年より、地方自治体の在宅就業支援事業にてPC基礎およびMicrosoft Office関連の教材作成およびeラーニング指導を担当。


Copyright© 2017 ITmedia, Inc. All Rights Reserved.

@IT Special

- PR -

TechTargetジャパン

この記事に関連するホワイトペーパー

Focus

- PR -

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。