Python
 Computer >> コンピューター >  >> プログラミング >> Python

Pythonでファイルを比較する方法|filecmpモジュールの使い方を徹底解説

はじめに

Pythonでファイル同士を比較したい場面は多くあります。たとえば、バックアップの整合性チェック、同期処理の検証、テストデータの確認などです。Pythonには標準ライブラリとしてfilecmpモジュールが用意されており、これを使うことでファイルやディレクトリを簡単かつ効率的に比較できます。

本記事では、filecmpモジュールの主要な関数・クラスであるcmp()cmpfiles()dircmpの使い方を、実際のコード例と実行結果とともにわかりやすく解説します。

filecmpモジュールとは

filecmpモジュールは、ファイルおよびディレクトリの比較を行うための標準ライブラリです。追加インストールは不要で、importするだけですぐに利用できます。

cmp()関数:2つのファイルを比較する

cmp(file1, file2[, shallow])

cmp()は、指定された2つのファイルfile1とfile2を比較し、同一であればTrue、異なっていればFalseを返します。

  • デフォルトでは、os.stat()が返すメタデータ(サイズ、更新日時など)が一致するファイルを「同一」とみなします。

  • 引数shallowが指定されない場合、またはTrueの場合は浅い比較(shallow comparison)が行われ、statシグネチャが同じファイルは同一と判断されます。

  • shallow=Falseを指定すると深い比較となり、ファイルの中身そのものが必ず比較されます。

注意: 浅い比較では、サイズと作成時刻が同じであれば中身が異なっていても「同一」と判定されることがあります。確実に内容を比較したい場合はshallow=Falseを使用しましょう。

cmpfiles()関数:複数のファイルを一括比較する

cmpfiles(dir1, dir2, common[, shallow])

cmpfiles()は、2つのディレクトリdir1とdir2に含まれる、リストcommon内のファイル群の内容を比較します。戻り値は、ファイル名のリストを3つ持つタプルです。

  • match … 両ディレクトリで内容が一致しているファイルの一覧

  • mismatch … 内容が一致しないファイルの一覧

  • errors … 何らかの理由で比較できなかったファイルの一覧

dircmpクラス:ディレクトリ同士を詳細に比較する

dircmp(dir1, dir2 [, ignore[, hide]])

dircmpは、ディレクトリdir1とdir2に対してさまざまな比較操作を実行できるディレクトリ比較オブジェクトを生成します。

  • ignore … 比較時に無視するファイル名のリスト。デフォルト値は ['RCS', 'CVS', 'tags']

  • hide … 結果から非表示にするファイル名のリスト。デフォルト値は [os.curdir, os.pardir](UNIXでは ['.', '..'])。

dircmpオブジェクトの主なメソッド

dircmpのインスタンスは、sys.stdoutへ詳細なレポートを出力する以下のメソッドを持っています。

  • report() … 2つのディレクトリの比較結果を出力します。

  • report_partial_closure() … 2つのディレクトリに加え、それぞれの直下のサブディレクトリの比較結果も出力します。

  • report_full_closure() … 2つのディレクトリ、そのすべてのサブディレクトリ、さらにその下層のサブディレクトリまで再帰的に比較結果を出力します。

dircmpオブジェクトの主な属性

  • left_list … ディレクトリpath1で見つかったファイル・サブディレクトリの一覧(hidelistの要素は除く)

  • right_list … ディレクトリpath2で見つかったファイル・サブディレクトリの一覧(hidelistの要素は除く)

  • common … path1とpath2の両方に存在するファイル・サブディレクトリの一覧

  • left_only … path1にのみ存在するファイル・サブディレクトリの一覧

  • right_only … path2にのみ存在するファイル・サブディレクトリの一覧

  • common_dirs … path1とpath2の両方に存在するサブディレクトリの一覧

  • common_files … path1とpath2の両方に存在するファイルの一覧

  • same_files … 両ディレクトリで内容が完全に一致するファイルのパス一覧

  • diff_files … 両ディレクトリに存在するが内容が異なるファイルのパス一覧

  • funny_files … 両ディレクトリに存在するものの、何らかの理由で比較できなかったファイルのパス一覧

  • subdirs … common_dirsの名前をdircmpオブジェクトに対応付ける辞書

比較用テストデータの準備

まず、比較動作を確認するためのテスト用ディレクトリとファイルを作成します。以下のコードでは、「両ディレクトリに共通して存在するファイル」「片方にしか存在しないファイル」「同名だが内容が異なるファイル」などを意図的に混在させています。

import os

# prepare test data
def makefile(filename, text=None):
    """
    Function: make some files
    params : input file, body
    """
    with open(filename, 'w') as f:
        f.write(text or filename)
    return


# prepare test data
def makedirectory(directory_name):
    """
    Function: make directories
    params : input directory
    """
    if not os.path.exists(directory_name):
        os.mkdir(directory_name)


# Get current working directory
present_directory = os.getcwd()

# change to directory provided
os.chdir(directory_name)

# Make two directories
os.mkdir('dir1')
os.mkdir('dir2')

# Make two same subdirectories
os.mkdir('dir1/common_dir')
os.mkdir('dir2/common_dir')

# Make two different subdirectories
os.mkdir('dir1/dir_only_in_dir1')
os.mkdir('dir2/dir_only_in_dir2')

# Make a unique file one each in directory
makefile('dir1/file_only_in_dir1')
makefile('dir2/file_only_in_dir2')

# Make files with the same content in both directories
makefile('dir1/common_file', 'Hello, Writing Same Content')
makefile('dir2/common_file', 'Hello, Writing Same Content')

# Make files with different content under the same name
makefile('dir1/not_the_same')
makefile('dir2/not_the_same')

makefile('dir1/file_in_dir1', 'This is a file in dir1')

os.mkdir('dir2/file_in_dir1')

os.chdir(present_directory)

return

if __name__ == '__main__':
    os.chdir(os.getcwd())
    makedirectory('example')
    makedirectory('example/dir1/common_dir')
    makedirectory('example/dir2/common_dir')

cmp()関数の使用例

cmp()の引数shallowは、メタデータに加えてファイルの中身まで見るかどうかを制御します。デフォルトではos.stat()から得られる情報による浅い比較が行われ、結果が同じなら同一とみなされます。そのため、作成時刻とサイズが同じファイルは、内容が異なっていても「同一」と報告される点に注意してください。shallow=Falseを指定すると、常にファイルの中身が比較されます。

import filecmp

print('Output \n *** Common File :', end=' ')

print(filecmp.cmp('example/dir1/common_file',
                  'example/dir2/common_file'), end=' ')

print(filecmp.cmp('example/dir1/common_file',
                  'example/dir2/common_file', shallow=False))

print(' *** Different Files :', end=' ')

print(filecmp.cmp('example/dir1/not_the_same',
                  'example/dir2/not_the_same'), end=' ')

print(filecmp.cmp('example/dir1/not_the_same',
                  'example/dir2/not_the_same', shallow=False))

print(' *** Identical Files :', end=' ')

print(filecmp.cmp('example/dir1/file_only_in_dir1',
                  'example/dir1/file_only_in_dir1'), end=' ')

print(filecmp.cmp('example/dir1/file_only_in_dir1',
                  'example/dir1/file_only_in_dir1', shallow=False))

実行結果

*** Common File : True True
*** Different Files : False False
*** Identical Files : True True

このように、shallow=True(デフォルト)でもshallow=Falseでも、この例では同じ結果になっています。ただし、メタデータが偶然一致した別内容のファイルが存在する場合は、結果が分かれることがあります。

cmpfiles()関数の使用例

cmpfiles()を使うと、再帰処理なしに、2つのディレクトリ内の複数ファイルをまとめて比較できます。まず共通するファイルを抽出し、それらを比較対象として渡すのが基本的な流れです。

import filecmp
import os

# Determine the items that exist in both directories.
dir1_contents = set(os.listdir('example/dir1'))
dir2_contents = set(os.listdir('example/dir2'))
common = list(dir1_contents & dir2_contents)

common_files = [f for f in common if os.path.isfile(os.path.join('example/dir1', f))]

print(f' *** Common files are : {common_files}')

# Now, let us compare the directories
match, mismatch, errors = filecmp.cmpfiles(
    'example/dir1',
    'example/dir2',
    common_files,
)

print(f' *** Matched files are : {match}')
print(f' *** mismatch files are : {mismatch}')
print(f' *** errors files are : {errors}')

実行結果

*** Common files are : ['file_in_dir1', 'not_the_same', 'common_file']
*** Matched files are : ['common_file']
*** mismatch files are : ['file_in_dir1', 'not_the_same']
*** errors files are : []

結果を見ると、内容が一致しているのはcommon_fileだけで、file_in_dir1not_the_sameは不一致、エラーはゼロであることがわかります。

dircmpによるディレクトリの比較

最後に、dircmpクラスを使ってディレクトリ同士を比較してみましょう。report()で単一階層の比較結果を、report_full_closure()で全サブディレクトリを再帰的にたどった詳細な比較結果を取得できます。

import filecmp

dc = filecmp.dircmp('example/dir1', 'example/dir2')
print(f"output \n *** Printing detailed report: \n ")
print(dc.report())
print(f"\n")
print(dc.report_full_closure())

実行結果

*** Printing detailed report:

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']
Differing files : ['not_the_same']
Common subdirectories : ['common_dir']
Common funny cases : ['file_in_dir1']
None

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']
Differing files : ['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\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

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
None

レポートからは、どのファイル・サブディレクトリが片側にのみ存在し、どれが一致し、どれが不一致かが一目で把握できます。

まとめ

Pythonのfilecmpモジュールを使えば、ファイル比較を次の3つのレベルで柔軟に行えます。

  • cmp() … 2つのファイルを手軽に比較(shallow引数で浅い比較/深い比較を切り替え)

  • cmpfiles() … 2つのディレクトリ間で複数ファイルを一括比較

  • dircmp … ディレクトリ構造ごと詳細に分析し、レポート出力も可能

用途に応じてこれらの機能を使い分けることで、バックアップ検証や差分チェックなどの処理を効率的に実装できます。ぜひ、記事内の各メソッドを実際に試して、それぞれの挙動を確認してみてください。

  1. Pythonでディレクトリ内のファイル一覧を取得する方法:os.listdir()とos.walk()の使い方を徹底解説

    Pythonでファイルを扱っていると、「特定のディレクトリ内にどんなファイルがあるのか一覧を確認したい」という場面によく出会います。例えば、フォルダ内にあるPythonファイルだけを抽出したい場合などがその典型例です。 Pythonの標準ライブラリ「os」には、ディレクトリ内のファイルを一覧表示するための便利なメソッドが複数用意されています。この記事では、os.listdir()を使って指定したディレクトリ直下のファイルやフォルダを取得する方法と、os.walk()を使ってサブディレクトリまで含めたすべてのファイルを取得する方法を、具体例を交えながらわかりやすく解説します。 Pytho

  2. 【Python】pikepdfでパスワード保護されたPDFを解除する方法

    はじめに Pythonには、アプリケーション開発、Web開発、科学技術計算、ソフトウェアテスト、機械学習など、さまざまな用途に対応できる豊富なライブラリが揃っています。さらに情報セキュリティの分野でも、システムアプリケーションのテストや開発に活用されており、ハッシュ生成、情報収集・取得、暗号化・復号化、Webクローリング、スプーフィングなどに特化したスクリプトを備えたライブラリやツールが数多く公開されています。 本記事では、パスワードで保護されたPDFドキュメントを復号(ロック解除)するプログラムを作成します。復号には、よく使われるパスワードをまとめた「ワードリスト」を利用し、これを順番に試し