|||||||||||||||||||||

なんぶ電子

- 更新: 

ふたつのディレクトリの中身が一致するか

プロジェクトの途中からまたは完成したプロジェクトをコピーして別のプロジェクトを作ることはよくあることだと思います。

その際、それぞれに開発していったファイルの中身が一致するのかしないのか気になることもしかりではないでしょうか。

今回はそのようなケースに遭遇した際に使えるよう、ふたつのディレクトリを指定してその中のファイルが一致するかしないかをチェックするPythonのコードを用意してみました。

ディレクトリ全体でチェックをする

この後紹介するコードのロジックは、基本的にふたつのフォルダにあるファイルのハッシュを取得し一致するかを調べるものとなります。

まずはディレクトリ全体のハッシュ同士を比較してみます。

compare-dir.py

import os
import hashlib

def calculate_directory_hash(directory):
  #import した hashlib から sha256用のオブジェクトを取得
  hash_object = hashlib.sha256()
  
  # ハッシュを取得しながらディレクトリ内を走査
  for root, dirs, files in os.walk(directory):
    for file in files:
      file_path = os.path.join(root, file)
      try:
        with open(file_path, 'rb') as f:
          for chunk in iter(lambda: f.read(4096), b''):
            hash_object.update(chunk)
      except IOError:
        # ファイルにアクセスできない場合の処理
        pass

  return hash_object.hexdigest()

def compare_directories(directory1, directory2):
  hash1 = calculate_directory_hash(directory1)
  hash2 = calculate_directory_hash(directory2)

  if hash1 == hash2:
    print("ふたつのディレクトリは完全一致しました")
  else:
    print("ふたつのディレクトリ内には異なる箇所が存在します")

# エントリーポイント
directory1 = "dir1"
directory2 = "dir2"

compare_directories(directory1, directory2)

コード内にもコメントがありますが、もう少し詳しく処理を解説していきます。

import した os ライブラリから walk関数を使います。

これは、ディレクトリを引数にとり、再帰的にディレクトリをたどっていくジェネレーターを返します。

それをループさせて

現在のディレクトリのパス,サブディレクトリのリスト,ファイルのリスト

で構成されるタプルを取得しています。

ファイルをオープンし、lambda: を使って指定サイズを読み込む無名関数を作成しています。

iterは第一引数に渡した関数(先の無名関数)から、第二引数の値が返るまで反復をするイテレーターを生成する関数です。

for chunk in イテレーター とすることで、イテレーターのループをchunk変数で受け取ります。

chunk変数に受け取った値をupdateメソッドに細切れで渡したのち最後に、hexdigest()を呼ぶことにより連結された内容でのハッシュを得られます。

ファイル単位で違いを取得

ふたつのディレクトリが一致しなかった場合は、何が一致していないかの確認も必要だと思います。

そこで先のコードを改変して、今度はファイル単位で違いを検出してみます。

compare-files.py

import os
import hashlib

def calculate_file_hash(file_path):
  hash_object = hashlib.sha256()

  try:
    with open(file_path, 'rb') as f:
      for chunk in iter(lambda: f.read(4096), b''):
        hash_object.update(chunk)
  except IOError:
    # ファイルにアクセスできない場合の処理
    pass

  return hash_object.hexdigest()

def compare_directories(directory1, directory2):
  different_files = []
  files1 =[];
  
  for root, dirs, files in os.walk(directory1):
    for file in files:
      file_path1 = os.path.join(root, file)
      # relpathを使ってディレクトリ1からの相対パスを取得して、ディレクトリ2に連結
      file_path2 = os.path.join(directory2, os.path.relpath(file_path1, directory1))
      
      # 後からの比較用に相対パスを保持
      files1.append(os.path.relpath(file_path1, directory1))
      
      if not os.path.exists(file_path2):
        different_files.append(file_path1)
      else:
        hash1 = calculate_file_hash(file_path1)
        hash2 = calculate_file_hash(file_path2)
        if hash1 != hash2:
          different_files.append(file_path1)
  
  # ディレクトリ2に存在するファイルを取得(相対パス)
  files2 = get_files_in_directory(directory2)
  
  # 先ほど取得したリストと比較して2にだけ存在するファイルを取得
  only_dir2_files = list(set(files2) - set(files1))
  
  # 違いに追加
  for file in only_dir2_files:
    different_files.append(os.path.join(directory2,file))
    
  return different_files

# 指定したディレクトリにあるファイル一覧を相対パスで取得する
def get_files_in_directory(directory):
  ret_files = []
  for root, dirs, files in os.walk(directory):
      for file in files:
          file_path = os.path.join(root, file)
          # 相対パスを取得
          ret_files.append(os.path.relpath(file_path, directory))
  return ret_files

# エントリーポイント
directory1 = "dir1"
directory2 = "dir2"

different_files = compare_directories(directory1, directory2)

if different_files:
  print("次のファイルが異なります:")
  for file in different_files:
    print(file)
else:
  print("ディレクトリ内のファイルはすべて一致しています。")

ディレクトリ1を基準にして、ディレクトリ2で該当のファイルを探して存在しないか、ハッシュが違う場合は異なるファイルとして検出します。

ディレクトリ1を基準にする都合で、そのままではディレクトリ2にだけ存在するファイルは検出できないので、別途走査します。

その際も、os.path.existsを使えばいいのかもしれませんが、setにキャストして差集合を求めました。(その方がなんとなくディスクIOが少なくなるような気がしたので)

筆者紹介


自分の写真
がーふぁ、とか、ふぃんてっく、とか世の中すっかりハイテクになってしまいました。プログラムのコーディングに触れることもある筆者ですが、自分の作業は硯と筆で文字をかいているみたいな古臭いものだと思っています。 今やこんな風にブログを書くことすらAIにとって代わられそうなほど技術は進んでいます。 生活やビジネスでPCを活用しようとするとき、そんな第一線の技術と比べてしまうとやる気が失せてしまいがちですが、おいしいお惣菜をネットで注文できる時代でも、手作りの味はすたれていません。 提示されたもの(アプリ)に自分を合わせるのでなく、自分の活動にあったアプリを作る。それがPC活用の基本なんじゃなかと思います。 そんな意見に同調していただける方向けにLinuxのDebianOSをはじめとした基本無料のアプリの使い方を紹介できたらなと考えています。

広告