filecmp – ファイルを比較する¶
目的: | ファイルシステム上のファイルやディレクトリを比較する |
---|---|
利用できるバージョン: | 2.1 以上 |
サンプルデータ¶
この記事のサンプルは、次の filecmp_mkexamples.py で作成したテストファイルを使用します。
import os
def mkfile(filename, body=None):
with open(filename, 'w') as f:
f.write(body or filename)
return
def make_example_dir(top):
if not os.path.exists(top):
os.mkdir(top)
curdir = os.getcwd()
os.chdir(top)
os.mkdir('dir1')
os.mkdir('dir2')
mkfile('dir1/file_only_in_dir1')
mkfile('dir2/file_only_in_dir2')
os.mkdir('dir1/dir_only_in_dir1')
os.mkdir('dir2/dir_only_in_dir2')
os.mkdir('dir1/common_dir')
os.mkdir('dir2/common_dir')
mkfile('dir1/common_file', 'this file is the same')
mkfile('dir2/common_file', 'this file is the same')
mkfile('dir1/not_the_same')
mkfile('dir2/not_the_same')
mkfile('dir1/file_in_dir1', 'This is a file in dir1')
os.mkdir('dir2/file_in_dir1')
os.chdir(curdir)
return
if __name__ == '__main__':
os.chdir(os.path.dirname(__file__) or os.getcwd())
make_example_dir('example')
make_example_dir('example/dir1/common_dir')
make_example_dir('example/dir2/common_dir')
$ ls -Rlast example
total 0
0 drwxr-xr-x 4 dhellmann dhellmann 136 Apr 20 17:04 .
0 drwxr-xr-x 9 dhellmann dhellmann 306 Apr 20 17:04 ..
0 drwxr-xr-x 8 dhellmann dhellmann 272 Apr 20 17:04 dir1
0 drwxr-xr-x 8 dhellmann dhellmann 272 Apr 20 17:04 dir2
example/dir1:
total 32
0 drwxr-xr-x 8 dhellmann dhellmann 272 Apr 20 17:04 .
0 drwxr-xr-x 4 dhellmann dhellmann 136 Apr 20 17:04 ..
0 drwxr-xr-x 2 dhellmann dhellmann 68 Apr 20 17:04 common_dir
8 -rw-r--r-- 1 dhellmann dhellmann 21 Apr 20 17:04 common_file
0 drwxr-xr-x 2 dhellmann dhellmann 68 Apr 20 17:04 dir_only_in_dir1
8 -rw-r--r-- 1 dhellmann dhellmann 22 Apr 20 17:04 file_in_dir1
8 -rw-r--r-- 1 dhellmann dhellmann 22 Apr 20 17:04 file_only_in_dir1
8 -rw-r--r-- 1 dhellmann dhellmann 17 Apr 20 17:04 not_the_same
example/dir2:
total 24
0 drwxr-xr-x 8 dhellmann dhellmann 272 Apr 20 17:04 .
0 drwxr-xr-x 4 dhellmann dhellmann 136 Apr 20 17:04 ..
0 drwxr-xr-x 2 dhellmann dhellmann 68 Apr 20 17:04 common_dir
8 -rw-r--r-- 1 dhellmann dhellmann 21 Apr 20 17:04 common_file
0 drwxr-xr-x 2 dhellmann dhellmann 68 Apr 20 17:04 dir_only_in_dir2
0 drwxr-xr-x 2 dhellmann dhellmann 68 Apr 20 17:04 file_in_dir1
8 -rw-r--r-- 1 dhellmann dhellmann 22 Apr 20 17:04 file_only_in_dir2
8 -rw-r--r-- 1 dhellmann dhellmann 17 Apr 20 17:04 not_the_same
興味のある再帰比較オプションを試すために “common_dir” ディレクトリの配下には同じディレクトリ構造(dir1 と dir2)が置かれます。
ファイルを比較する¶
filecmp モジュールは、ファイルシステム上のファイルやクラスを比較する関数やクラスを提供します。2つのファイルを比較するには cmp() 関数を使用してください。
import filecmp
print 'common_file:',
print filecmp.cmp('example/dir1/common_file',
'example/dir2/common_file'),
print filecmp.cmp('example/dir1/common_file',
'example/dir2/common_file',
shallow=False)
print 'not_the_same:',
print filecmp.cmp('example/dir1/not_the_same',
'example/dir2/not_the_same'),
print filecmp.cmp('example/dir1/not_the_same',
'example/dir2/not_the_same',
shallow=False)
print 'identical:',
print filecmp.cmp('example/dir1/file_only_in_dir1',
'example/dir1/file_only_in_dir1'),
print filecmp.cmp('example/dir1/file_only_in_dir1',
'example/dir1/file_only_in_dir1',
shallow=False)
デフォルトでは、 cmp() は os.stat() から利用できる情報のみを調べます。shallow 引数は、そのファイルの中身を調べるかを cmp() へ伝えます。デフォルトは、ファイルの中身を調べずに浅い比較を実行します。ファイルの中身を比較しない場合、全く同時に作成された同じサイズのファイルは同一と見なされることに注意してください。
$ python filecmp_cmp.py
common_file: True True
not_the_same: True False
identical: True True
再帰せずに2つのディレクトリ内のファイルセットを比較するには filecmp.cmpfiles() を使用してください。その引数はディレクトリの名前と、その2つのディレクトリでチェックするファイルのリストです。共通ファイルのリストはファイル名のみを含めます(ディレクトリは必ずミスマッチになります)。そして、そのファイルは両方のディレクトリに存在していなければなりません。次のコードは、共通リストを作成するシンプルな方法を紹介します。もっと短く書けるならコメントで教えてください。 cmp() と同様に、shallow フラグを受け取って比較することもできます。
import filecmp
import os
# 両方のディレクトリに存在する要素を決定する
d1_contents = set(os.listdir('example/dir1'))
d2_contents = set(os.listdir('example/dir2'))
common = list(d1_contents & d2_contents)
common_files = [ f
for f in common
if os.path.isfile(os.path.join('example/dir1', f))
]
print 'Common files:', common_files
# ディレクトリを比較する
match, mismatch, errors = filecmp.cmpfiles('example/dir1',
'example/dir2',
common_files)
print 'Match:', match
print 'Mismatch:', mismatch
print 'Errors:', errors
cmpfiles() はマッチしたファイル、マッチしなかったファイル、(パーミッションや何らかの理由で)比較できなかったファイルのファイル名を含む3つのリストを返します。
$ python filecmp_cmpfiles.py
Common files: ['not_the_same', 'file_in_dir1', 'common_file']
Match: ['not_the_same', 'common_file']
Mismatch: ['file_in_dir1']
Errors: []
dircmp を使用する¶
前節で紹介した関数は相対的にシンプルな比較に適していますが、巨大なディレクトリツリーの再帰的な比較、またはもっと複雑な解析には dircmp クラスがさらに便利です。最も簡単な使用方法は、 report() メソッドで2つのディレクトリを比較するレポートを表示できます。
import filecmp
filecmp.dircmp('example/dir1', 'example/dir2').report()
その実行結果は、再帰せずに渡されたディレクトリのコンテンツの比較結果を表示するプレーンテキストのレポートです。このケースでは、”not_the_same” というファイルが同一と見なされます。それはそのファイルの中身が比較されないからです。dircmp には cmp() のようなファイルの中身を比較する方法がありません。
$ python filecmp_dircmp_report.py
diff example/dir1 example/dir2
Only in example/dir1 : ['dir_only_in_dir1', 'file_only_in_dir1']
Only in example/dir2 : ['dir_only_in_dir2', 'file_only_in_dir2']
Identical files : ['common_file', 'not_the_same']
Common subdirectories : ['common_dir']
Common funny cases : ['file_in_dir1']
さらに詳細に、再帰的な比較を行うには report_full_closure() を使用してください。
import filecmp
filecmp.dircmp('example/dir1', 'example/dir2').report_full_closure()
その実行結果は、全てのサブディレクトリを比較します。
$ python filecmp_dircmp_report_full_closure.py
diff example/dir1 example/dir2
Only in example/dir1 : ['dir_only_in_dir1', 'file_only_in_dir1']
Only in example/dir2 : ['dir_only_in_dir2', 'file_only_in_dir2']
Identical files : ['common_file', 'not_the_same']
Common subdirectories : ['common_dir']
Common funny cases : ['file_in_dir1']
diff example/dir1/common_dir example/dir2/common_dir
Common subdirectories : ['dir1', 'dir2']
diff example/dir1/common_dir/dir2 example/dir2/common_dir/dir2
Identical files : ['common_file', 'file_only_in_dir2', 'not_the_same']
Common subdirectories : ['common_dir', 'dir_only_in_dir2', 'file_in_dir1']
diff example/dir1/common_dir/dir2/common_dir example/dir2/common_dir/dir2/common_dir
diff example/dir1/common_dir/dir2/dir_only_in_dir2 example/dir2/common_dir/dir2/dir_only_in_dir2
diff example/dir1/common_dir/dir2/file_in_dir1 example/dir2/common_dir/dir2/file_in_dir1
diff example/dir1/common_dir/dir1 example/dir2/common_dir/dir1
Identical files : ['common_file', 'file_in_dir1', 'file_only_in_dir1', 'not_the_same']
Common subdirectories : ['common_dir', 'dir_only_in_dir1']
diff example/dir1/common_dir/dir1/common_dir example/dir2/common_dir/dir1/common_dir
diff example/dir1/common_dir/dir1/dir_only_in_dir1 example/dir2/common_dir/dir1/dir_only_in_dir1
プログラム内で差異を使用する¶
レポートの作成に加えて、dircmp は直接プログラム内で利用可能な便利なファイルリストを算出します。それぞれの属性はそれが要求されたときのみ算出されるので、dircmp をインスタンス化するのはそう大きなオーバーヘッドになりません。
比較対象のディレクトリを含むサブディレクトリとファイルは left_list と right_list で表示されます。
import filecmp
dc = filecmp.dircmp('example/dir1', 'example/dir2')
print 'Left :', dc.left_list
print 'Right:', dc.right_list
$ python filecmp_dircmp_list.py
Left : ['common_dir', 'common_file', 'dir_only_in_dir1', 'file_in_dir1', 'file_only_in_dir1', 'not_the_same']
Right: ['common_dir', 'common_file', 'dir_only_in_dir2', 'file_in_dir1', 'file_only_in_dir2', 'not_the_same']
無視するファイル名のリストをコンストラクタへ渡すことでその入力をフィルタできます。デフォルトでは、RCS, CVS や tags が無視されます。
import filecmp
dc = filecmp.dircmp('example/dir1', 'example/dir2', ignore=['common_file'])
print 'Left :', dc.left_list
print 'Right:', dc.right_list
このケースでは、”common_file” が比較対象外のファイルリストにあります。
$ python filecmp_dircmp_list_filter.py
Left : ['common_dir', 'dir_only_in_dir1', 'file_in_dir1', 'file_only_in_dir1', 'not_the_same']
Right: ['common_dir', 'dir_only_in_dir2', 'file_in_dir1', 'file_only_in_dir2', 'not_the_same']
両方の入力ディレクトリの共通ファイルは common で保持されます。それぞれのディレクトリにしか存在しないファイルは left_only や right_only で表示されます。
import filecmp
dc = filecmp.dircmp('example/dir1', 'example/dir2')
print 'Common:', dc.common
print 'Left :', dc.left_only
print 'Right :', dc.right_only
$ python filecmp_dircmp_membership.py
Common: ['not_the_same', 'common_file', 'file_in_dir1', 'common_dir']
Left : ['dir_only_in_dir1', 'file_only_in_dir1']
Right : ['dir_only_in_dir2', 'file_only_in_dir2']
common の仲間には、さらにファイル、ディレクトリ、”funny” (2つのディレクトリ、または os.stat() からエラーが発生した場所で異なる型を持つ) の要素に分割されます。
import filecmp
dc = filecmp.dircmp('example/dir1', 'example/dir2')
print 'Common :', dc.common
print 'Directories:', dc.common_dirs
print 'Files :', dc.common_files
print 'Funny :', dc.common_funny
このサンプルデータでは、”file_in_dir1” という要素はディレクトリ内にあるファイルであり、且つサブディレクトリです。そのため “funny” リストに表示されます。
$ python filecmp_dircmp_common.py
Common : ['not_the_same', 'common_file', 'file_in_dir1', 'common_dir']
Directories: ['common_dir']
Files : ['not_the_same', 'common_file']
Funny : ['file_in_dir1']
ファイル間の差異は同様に分割されます。
import filecmp
dc = filecmp.dircmp('example/dir1', 'example/dir2')
print 'Same :', dc.same_files
print 'Different :', dc.diff_files
print 'Funny :', dc.funny_files
“not_the_same” は os.stat() のみで比較されたファイルであり、そのファイルの中身は調べないことを覚えておいてください。
$ python filecmp_dircmp_diff.py
Same : ['not_the_same', 'common_file']
Different : []
Funny : []
最後に、再帰的な比較が簡単にできるようにサブディレクトリは subdirs 属性で新たな dircmp オブジェクトを取得することもできます。
import filecmp
dc = filecmp.dircmp('example/dir1', 'example/dir2')
print 'Subdirectories:'
print dc.subdirs
$ python filecmp_dircmp_subdirs.py
Subdirectories:
{'common_dir': <filecmp.dircmp instance at 0x85da0>}
See also
- filecmp
- 本モジュールの標準ライブラリドキュメント
- Directories from os
- ディレクトリのコンテンツを表示する
- difflib
- 2つのシーケンス間の差異を算出する