結論から言うと、shutil はファイルやフォルダのコピー、移動、削除などを扱うための標準ライブラリです。

Pythonで作業を自動化すると、処理前にファイルをバックアップしたり、出力ファイルを別フォルダへコピーしたりすることがあります。
そのような場面では、shutil.copy2()shutil.copytree() を使うと、少ないコードで実用的なコピー処理を書けます。
この記事では、テキストファイルをバックアップフォルダへコピーする例を使って、shutil の基本を整理します。

この記事でわかること

  • shutil でファイルをコピーする基本
  • copy()copy2()copytree() の使い分け
  • 実務でバックアップ処理を書くときの注意点

完成コード

完成コードは次の通りです。

import shutil
from datetime import datetime
from pathlib import Path


def backup_file(source_path: Path, backup_dir: Path) -> Path:
    if not source_path.is_file():
        raise FileNotFoundError(f"コピー元ファイルが見つかりません: {source_path}")

    backup_dir.mkdir(parents=True, exist_ok=True)

    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    backup_name = f"{source_path.stem}_{timestamp}{source_path.suffix}"
    backup_path = backup_dir / backup_name

    shutil.copy2(source_path, backup_path)
    return backup_path


work_dir = Path("work")
source_file = work_dir / "report.txt"
source_file.parent.mkdir(parents=True, exist_ok=True)
source_file.write_text("今日の作業メモです。", encoding="utf-8")

saved_path = backup_file(source_file, work_dir / "backup")
print(f"バックアップしました: {saved_path}")

このコードを実行すると、work/report.txt を作成し、work/backup フォルダに日時付きのファイル名でコピーします。

コードのポイント

このコードでは、pathlib でパスを組み立て、shutil.copy2() でファイルをコピーしています。

  • コピー元がファイルかどうかを is_file() で確認しています
  • バックアップ先フォルダを mkdir() で作成しています
  • 日時を付けて、上書きしにくいファイル名にしています
  • shutil.copy2() でファイルの内容とメタ情報をコピーしています
  • コピーした先のパスを戻り値として返しています

実務では、コピー処理そのものよりも、コピー前の確認や上書き対策が大切です。
安全に扱うために、コピー元の存在確認と保存先の命名をセットで考えます。

コードを順番に説明します

主要な処理を分けて説明します。

1. コピー元ファイルを確認します

if not source_path.is_file():
    raise FileNotFoundError(f"コピー元ファイルが見つかりません: {source_path}")

source_path.is_file() は、指定したパスが実在するファイルかどうかを確認します。
存在しないファイルをコピーしようとするとエラーになるため、先に確認しておくと原因が分かりやすくなります。

ここでは、見つからない場合に FileNotFoundError を出しています。
第4章で扱った raise の考え方と同じで、処理を続けられない状態を早めに知らせるためです。

2. バックアップ先フォルダを作ります

backup_dir.mkdir(parents=True, exist_ok=True)

コピー先フォルダが存在しない場合、コピー処理は失敗します。
そのため、コピーする前に mkdir() でフォルダを作成しています。

parents=True は途中のフォルダも作る指定です。
exist_ok=True は、すでにフォルダがある場合にエラーにしない指定です。
バックアップ処理では毎回同じフォルダを使うことが多いため、この指定がよく使われます。

3. 上書きしにくいファイル名を作ります

timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
backup_name = f"{source_path.stem}_{timestamp}{source_path.suffix}"
backup_path = backup_dir / backup_name

同じ名前でコピーすると、既存のバックアップを上書きしてしまう可能性があります。
そこで、元のファイル名に日時を付けて保存しています。

source_path.stem は拡張子を除いたファイル名です。
source_path.suffix.txt のような拡張子です。
この2つを使うと、元の名前を残しながら別名のバックアップファイルを作れます。

4. shutil.copy2でコピーします

shutil.copy2(source_path, backup_path)

shutil.copy2() は、ファイルの内容に加えて、更新日時などのメタ情報もできるだけコピーします。
単純に内容だけコピーしたい場合は shutil.copy() も使えます。

バックアップ用途では、元ファイルの情報を残したいことがあるため、copy2() を選ぶことが多いです。
ただし、OSやファイルシステムによって、すべての情報が完全に同じになるとは限りません。

実務で使うときのポイント

実務で shutil を使う場面は、バックアップ、成果物の配置、フォルダの複製、処理済みファイルの移動などです。
人が手作業でコピーしている定型作業は、shutil で自動化しやすい候補になります。

ファイルをコピーするときは、上書きの扱いを必ず考えます。
上書きしてよい処理なのか、日時付きで残すべきなのか、同名ファイルがあればエラーにするべきなのかを決めてからコードにすると、後から困りにくくなります。

フォルダごとコピーしたい場合は、shutil.copytree() を使います。
ただし、フォルダ単位のコピーは影響範囲が大きくなりやすいため、コピー元とコピー先を表示してから実行するなど、確認しやすい形にしておくと安心です。

よくある勘違い・注意点

  • shutil.copy() はファイルをコピーする関数で、フォルダ全体のコピーには copytree() を使います
  • コピー先に同名ファイルがあると、上書きされる場合があります
  • copy2() でも、すべてのメタ情報が完全に保存されるとは限りません
  • 削除系の shutil.rmtree() は影響が大きいため、コピー処理に慣れてから慎重に扱います

まとめ

  • shutil は、ファイルやフォルダのコピーなどを扱う標準ライブラリです
  • ファイルコピーでは、shutil.copy()shutil.copy2() を使います
  • バックアップ用途では、日時付きのファイル名にすると上書きを避けやすくなります
  • 実務では、コピー元の確認、コピー先フォルダの作成、上書きルールをセットで考えることが大切です