UNIX/Linuxの部屋 diffコマンドの使い方

TOP UNIX/Linuxの部屋 UNIX/Linuxコマンド一覧 用語集 新版 由来/読み方辞書 環境変数マニュアル Cシェル変数 システム設定ファイル システムコール・ライブラリ ネットワークプログラミングの基礎知識 クラウドサービス徹底比較・徹底解説




コマンド diff ファイルの違いを表示。パッチを作成。(差分・差異・比較) このエントリーをはてなブックマークに追加

最終更新


UNIX/Linux における diff コマンドは、2つのテキストファイルの違い (差分) を表示するコマンドである。ファイル内容が一致しているかの確認も可能。ディレクトリを再帰的にたどり、複数のファイルを一括して差分表示することもできる。


diff コマンドの基本的な使い方
2つのファイルの違いを表示したいとき、
% diff file1 file2
1c1
< modify
---
> modify2
3d2
< remove
5a5
> add
とすると、file1 と file2 の相違点を表示する。上記の例では、
  • file1 の modify が、file2 では modify2 に変わった
  • file2 にあった remove が、file2 では削除された
  • file1 になかった add が、file2 で追加された
ということを表している。"<" や ">" の方向がわかりづらい場合、diff -u を使うと "+" "-" になるので、こちらの方が直感的にわかりやすいのではないだろうか。
% diff -u file1 file2
--- file1 2017-03-21 03:43:42.361439427 +0000
+++ file2 2017-03-21 03:43:54.937869712 +0000
@@ -1,5 +1,5 @@
-modify
+modify2
a
-remove
c
d
+add

システムの設定ファイルをいじる場合は、最初に
% cp rc.conf rc.conf.org
などとオリジナルを保存し、適宜
% diff rc.conf.org rc.conf
として差分を取ることで、修正点を確認するようにしておくとよいだろう (バージョン管理しておくのが一番よい)。ちなみに "diff [新ファイル] [旧ファイル]" ではなく、"diff [旧ファイル] [新ファイル]" とするのが一般的。

パイプを使って標準入力と比較
ファイル名として - を指定すると標準入力と比較する。例えば
% command | diff - file
は command の出力と file の内容を比較する。

コマンドの出力結果同士の差分
コマンドの出力結果同士の差分を見たい場合は、bash であれば
% diff <(cmd1 a.txt) <(cmd2 b.txt)
とする。括弧の中は
% diff <(cmd1 a.txt | cmd2 | cmd3) <(cmd4 b.txt | cmd5 | cmd6)
のようにパイプでもなんでも使える。

bash 以外の sh 系シェルであれば、
% cmd1 | ( cmd2 | diff /dev/fd/3 -) 3<&0
とすればよい。

ディレクトリの差分
引数の両方にディレクトリ名を指定した場合は、2つのディレクトリ以下に存在するファイルの差分を表示する。
% diff dir1/ dir2/
また、-r オプションを指定することで、再帰的にサブディレクトリをたどっていく。例えば、
dir1/file1
dir1/subdir1/
dir1/subdir1/subfile1
dir1/subdir1/subfile2
dir2/file1
dir2/file2
dir2/subdir1/
dir2/subdir1/subfile1
dir2/subdir2/subfile1
というファイルが存在したとき、
% diff dir1/ dir2/
では、dir1/file1 と dir2/file1 の差分しか表示しない。
% diff -r dir1/ dir2/
だと、dir1/file1 と dir2/file1、dir1/subdir1/subfile1 と dir2/subdir1/subfile1 の差分しか表示しない。

特定のファイル名を除外したい場合、-x または --exclude オプションを使い、
% diff -r --exclude="*.png" dir1/ dir2/
→ *.png を除外
% diff -r --exclude=".svn" --exclude=".git" --exclude="*.log" dir1/ dir2/
→ .svn、.git、*.log を除外。exclude を複数指定してもよい。
などと除外することができる。

パッチ作成
パッチを作成するときにも diff コマンドが使える。まず最初にパッチの形式について説明せねばなるまい。パッチには主に context 形式、unified 形式の2種類がある (他にもノーマル形式と ed 形式があるが省略)。file.txt.org と file.txt という2つのファイルを例にあげる。
% cat file.txt.org
abc
def
hij
% cat file.txt
abc
XXX
hij
file.txt.org と file.txt の違いは2行目の「def」と「XXX」だけである。オプションを付けずに diff コマンドを実行すると、
% diff file.txt.org file.txt
2c2
< def
---
> XXX
となる。これがノーマル形式である。出力を context 形式にするには -c または --context オプションを付ける。
% diff -c file.txt.org file.txt (context 形式で出力)
*** file.txt.org Sun Sep 12 07:43:26 1999
--- file.txt Sun Sep 12 07:43:34 1999
***************
*** 1,3 ****
abc
! def
hij
--- 1,3 ----
abc
! XXX
hij
という形式になる。一方、-u または --unified オプションを付けると unified 形式になり、
% diff -u file.txt.org file.txt (unified 形式で出力)
--- file.txt.org Sun Sep 12 07:43:26 1999
+++ file.txt Sun Sep 12 07:43:34 1999
@@ -1,3 +1,3 @@
abc
-def
+XXX
hij
と、その名の通り unified (統一された) な出力が得られる。context diff や unified diff は、ノーマル形式と違い、「どのファイルの差分であるか」という情報が含まれているが、ノーマル形式 (オプションを付けないで実行) や ed 形式 (-eオプション) では、ファイルの情報が含まれていない。

つまり、ノーマル形式や ed 形式のパッチを patch コマンドで当てようとすると、パッチを当てるべきファイル名をユーザ自身が入力しなければならない。しかし、context 形式や unified 形式なら、パッチファイル自体にパッチの対象となるファイルの情報が含まれているので、わざわざユーザがファイル名を指定する必要はない。よって、パッチを作成する際は、context か unified 形式を使うべきである。なお、context と unified のどちらを使うかは好みの問題。以降の例では unified 形式を使うが、特に理由はない。

では、本題であるパッチの作り方を説明する。file.txt.org と file.txt のパッチを patch.txt に保存する。
% diff -u file.txt.org file.txt > patch.txt
ここで大事なのは、引数で指定する file.txt.org と file.txt の順番である。パッチを作るということは、誰かにパッチを当てて欲しいわけである。その「誰か」の手元には、当然どちらかのファイルしか存在しない (両方あるならパッチを当てる必要はない)。引数の最初に指定するのは、
「元のファイルと同じ内容のファイル名」(from-file)
2番目に指定するのは
「改変後のファイル名」(to-file)
である。

パッチを当てる人の手元には、file.txt という名前の
abc
def
hij
というファイルがあるはずである。一方、こちらの手元には、このファイルと同じ内容の file.txt.org と、内容を変更した新しい file.txt がある。よって、
% diff -u file.txt.org file.txt
という順番にしなければいけない。この順番を逆にするとリバースパッチになってしまう。
リバースパッチ・パッチの当て方は、patch コマンドの説明を参照してほしい。

オプション - 表示方法
-c または --context
context diff 形式の出力をする。"-c 5"、"--context=5" で、差異の前後 5行を表示する。
-u または --unified
unified diff 形式の出力をする。"-u 5"、"--unified=5" で、差異の前後 5行を表示する。
-e または --ed
ed diff 形式の出力をする。普通は使わない。
-y または --side-by-side
2つのファイルを、横に並べて表示する。十分な端末の横幅がないと見にくくなってしまう。-W オプションで横幅を指定できる。
-W または --width
-y オプションを付けたときの、横幅を指定。
% diff -y -W60 file1 file2
⇒ 差分を横に並べて表示。横幅は60文字。
オプション - 空白・タブ・改行・大文字小文字など、特定差異を無視する
-b または --ignore-space-change
空白 (スペース)・タブの数の違いを無視する。
-w または --ignore-all-space
空白 (スペース)・タブの有無を完全に無視する。"abc" と "a b c" が同じとなる。
-i または --ignore-case
大文字・小文字の違いを無視する。
-B または --ignore-blank-lines
空行のある・なしの違いを無視する。
--strip-trailing-cr
改行コード CR を削除する。LF と CR+LF が混在したまま比較した場合に使う。改行コードは一般的には UNIX では LF、DOS/Windows では CR+LF、Mac では CR。このオプションでは Mac 式の CR な改行コードはうまく扱えない。あきらめて tr・sed・perl などで改行コードを変換しよう。
-I または --ignore-matching-lines=[正規表現]
正規表現にマッチした行は無視する (差分としては除外する)。
% diff --ignore-matching-lines="last-modified: [-0-9/ :]+" out-20170321.txt out-20170322.txt
→ last-modified: YYYY/MM/DD HH:MM:SS という部分は毎回変わるので、その行は無視する。

オプション - 対象ファイルの絞り込み
-x [ファイルパターン] または --exclude=[ファイルパターン]
特定のファイル・ディレクトリパターンは無視する。ディレクトリの比較時、一括して除外したいファイルを指定する。
% diff -r --exclude="*.png" dir1/ dir2/
→ *.png を除外
% diff -r --exclude=".svn" --exclude=".git" --exclude="*.log" dir1/ dir2/
→ .svn、.git、*.log を除外。exclude を複数指定してもよい。
% diff -r --exclude="*.[ao]" dir1/ dir2/
→ *.a と *.o を除外

オプション - その他
-r または --recursive
ディレクトリを比較したとき、その下のサブディレクトリを再帰的にたどっていく。
-a または --text
テキストファイルとして比較する。
-q または --brief
ファイルが異なる場合、差分を表示せず、異なる旨を表示する。
% diff -q a.txt b.txt
Files a.txt and b.txt differ
→ あるいは「ファイル a.txt と b.txt は異なります」
-s または --report-identical-files
ファイルが同じな場合、その旨表示する。
% diff -s a.txt a.same.txt
Files a.txt and a.same.txt are identical
→ あるいは「ファイル a.txt と a.same.txt は同一です」
-q (--brief) と -s (--report-identical-files) を同時に使うと、全ファイルについて、同じか異なるかを表示してくれる。

Howto: diff コマンドで mysqldump の差分を調べる
mydevdb が開発用 DB、myproddb が本番用 DB だとすると、
% diff -F "^CREATE" -c -I "Dump completed on" \
<(mysqldump mydevdb -no-data | sed 's/\(AUTO_INCREMENT=\)[0-9][0-9]*/\1/') \
<(mysqldump myproddb --no-data | sed 's/\(AUTO_INCREMENT=\)[0-9][0-9]*/\1/')
などとするとよい。結果は
*************** CREATE TABLE `hoge_table` (
*** 71,76 ****
--- 71,77 ----
`last_checked_datetime` datetime DEFAULT NULL,
`last_notified_datetime` datetime DEFAULT NULL,
`notified_last_days` int(11) DEFAULT NULL,
+ `add_column` int(11) DEFAULT NULL,
`insert_datetime` datetime NOT NULL,
`update_datetime` datetime NOT NULL,
PRIMARY KEY (`uid`,`domain_id`)
のようになる。

工夫した箇所は以下のとおり。
  • あるテーブルが数十個カラムがあるとして、その真ん中あたりで差分が出た場合、どのテーブルに差分があるのかわからなくなる。よって、-F オプションで "^CREATE" を指定することで、差分があった箇所の直近にある ^CREATE にマッチする行を表示することで、上記例だと hoge_table に差分ありというのがわかりやすくなる。
  • ダンプ末尾に "-- Dump completed on 2017-04-03 13:44:47" などと表示が出る部分が差分として表示されないよう、-I オプションで無視している。
  • AUTO_INCREMENT=9999 などの値は環境によって異なる可能性が高いため、数字をつぶしている。
なお、インストールが必要であるものの、mysqldiff という差分チェック用のコマンドも存在する。

バイナリファイルのパッチ生成には、xdelta や bsdiff を使う。
>> Linuxオンラインマニュアル(man) Linux diff(1)
>> FreeBSDオンラインマニュアル(man) FreeBSD diff(1)
>> Solaris10オンラインマニュアル(man) Solaris10 diff(1)