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


※空白区切りで AND 検索 (例:「ファイル 削除」)

コマンド diff ファイルの違いを表示。パッチを作成。(差分・差異・比較)

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

基本的な使い方
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 形式の出力をする。普通は使わない。
-r または --recursive
ディレクトリを比較したとき、その下のサブディレクトリを再帰的にたどっていく。
-a または --text
テキストファイルとして比較する。
-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 などで改行コードを変換しよう。
-y または --side-by-side
2つのファイルを、横に並べて表示する。十分な端末の横幅がないと見にくくなってしまう。-W オプションで横幅を指定できる。
-W または --width
-y オプションを付けたときの、横幅を指定。
% diff -y -W60 file1 file2
⇒ 差分を横に並べて表示。横幅は60文字。
-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) を同時に使うと、全ファイルについて、同じか異なるかを表示してくれる。
-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 を除外

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


頑張って書いたおすすめコンテンツ!
クラウドサービス徹底比較・徹底解説