Pythonでフォルダ間のファイル差分抽出関数を作ってみた

2つの環境でプログラムファイルの同期をとる必要があったので、Pythonで差分抽出関数をつくってみました。

## フォルダ間のファイル差分抽出関数のソースコード

source_folderに比較元、target_folderに比較先のフォルダを指定します。
対象ファイルの選択でワイルドカードを使用することが可能です。

```python
import os
import glob

def folder_diff(source_folder, target_folder, wildcard_pattern='*'):
    """
    指定された2つのフォルダ間の差分を抽出します。

    Args:
        source_folder (str): 比較元のフォルダのパス。
        target_folder (str): 比較先のフォルダのパス。
        wildcard (str): ワイルドカード文字列（デフォルトは '*'）。

    Returns:
        tuple: (比較元にのみ存在するファイルのリスト, 比較先にのみ存在するファイルのリスト)。
    """
     # 比較元フォルダ内のファイルを取得
    source_files = set(glob.glob(os.path.join(source_folder, '**', wildcard_pattern), recursive=True))

    # 比較先フォルダ内のファイルを取得
    target_files = set(glob.glob(os.path.join(target_folder, '**', wildcard_pattern), recursive=True))

    # 取得したファイルパスをsource_folderを基準とした相対パスに変換
    source_files = {os.path.relpath(file, source_folder) for file in source_files}

    # 取得したファイルパスをtarget_folderを基準とした相対パスに変換
    target_files = {os.path.relpath(file, target_folder) for file in target_files}

    # 比較元にのみ存在するファイルを抽出
    unique_source_files = source_files - target_files
    
    # 比較先にのみ存在するファイルを抽出
    unique_target_files = target_files - source_files

    # 比較元と比較先でタイムスタンプかファイルサイズが異なるファイルを抽出
    different_timestamp = {}
    for file in source_files.intersection(target_files):
        source_path = os.path.join(source_folder, file)
        target_path = os.path.join(target_folder, file)
        if os.path.isfile(source_path) and os.path.isfile(target_path):
            source_stat = os.stat(source_path)
            target_stat = os.stat(target_path)
            if source_stat.st_mtime > target_stat.st_mtime or source_stat.st_size != target_stat.st_size:
                different_timestamp[file] = (source_stat.st_mtime, target_stat.st_mtime)

    # 結果を返す
    return list(unique_source_files), list(unique_target_files),list(different_timestamp)
```
使い方は次の通りです。

## 差分抽出関数の使用例
```python
# 使用例
source_folder = "o:/data"
target_folder = "p:/data"
unique_source_files, unique_target_files ,different_timestamp= folder_diff(source_folder, target_folder,"*.txt")
print("比較元にのみ存在するファイル:", unique_source_files)
print("比較先にのみ存在するファイル:", unique_target_files)
print("比較元と比較先でタイムスタンプかファイルサイズが異なるファイル:", different_timestamp)
```

## ファイルコピー関数のソースコード

差分抽出関数の結果を引数に渡すことで、ファイルのコピーを行います。

```python
def copy_files(source_folder, target_folder, files_to_copy):
    """
    指定されたファイルリストを、比較先フォルダにコピーします。

    Args:
        source_folder (str): 比較元のフォルダのパス。
        target_folder (str): 比較先のフォルダのパス。
        files_to_copy (list): コピーするファイルのリスト。

    Returns:
        None
    """
    for file in files_to_copy:
        source_path = os.path.join(source_folder, file)
        target_path = os.path.join(target_folder, file)
        print(f"コピー {source_path} => {target_path}")
        # ファイルをコピーする
        shutil.copy2(source_path, target_path)
```

## ファイルコピー関数の使用例

例えば、target_folder に存在しないか、タイムスタンプが古いか、サイズが異なるファイルをコピーしたい場合は、引数に unique_source_files+different_timestampを渡します。

```python
import shutil

copy_files(source_folder,target_folder,unique_source_files+different_timestamp)

```

## まとめ
Chat GPT を使ってサクッと作ろうとしたのですが、生成されたコードの出来が悪かったので、次の記事に掲載しているサンプルソースをChat GPTに投入することで形になりました。

参考：[【実用】Pythonファイル／フォルダ操作術。コピペで使えるサンプルコード付き。](https://resanaplaza.com/2024/02/03/%e3%80%90%e5%ae%9f%e7%94%a8%e3%80%91python%e3%83%95%e3%82%a1%e3%82%a4%e3%83%ab%ef%bc%8f%e3%83%95%e3%82%a9%e3%83%ab%e3%83%80%e6%93%8d%e4%bd%9c%e8%a1%93%e3%80%82%e3%82%b3%e3%83%94%e3%83%9a%e3%81%a7/)