基礎から学ぶPython入門 90日コース | 実践Python - Day 18:shutil

90日で身につけるPython
スポンサーリンク
スポンサーリンク
  1. Day 18:shutilで「ファイルとフォルダをまとめて扱う」感覚を身につける
  2. shutilとは何か ― 「まとめてファイル操作するための道具」
  3. ファイルコピー:バックアップの基本操作
    1. 単純なファイルコピー(copy)
    2. メタ情報も含めてコピーする(copy2)
  4. ファイル・フォルダの移動:整理やリネームに使う
    1. ファイルを別フォルダへ移動する(move)
    2. ファイル名の変更(リネーム)にも使える
  5. 削除:フォルダを中身ごと消すときの注意点
    1. フォルダを丸ごと削除する(rmtree)
  6. フォルダコピー:プロジェクト丸ごとバックアップする
    1. copytree() でフォルダを丸ごとコピーする
  7. Day 18ミニテンプレート:簡易バックアップ&整理ツール
  8. Day 18のまとめ
  9. Day 18:shutilで「自動バックアップツール」を作る
  10. 自動バックアップツールの全体像をイメージする
    1. どんなことをするツールか
  11. バックアップ元・先フォルダを指定する
    1. pathlibでパスを扱う
  12. 日付入りのバックアップフォルダを作る
    1. datetimeで「バックアップ名」を作る
  13. フォルダ丸ごとコピー:copytreeでバックアップを取る
    1. shutil.copytree() を使ったバックアップ
  14. 古いバックアップを削除する:世代管理
    1. 最新N世代だけ残すロジック
  15. 一時ファイルの移動・削除(オプション機能)
    1. 一時ファイルを別フォルダへ移動する
    2. 一時ファイルを削除する
  16. 自動バックアップツールの全体テンプレート
  17. Day 18(制作編)のまとめ

Day 18:shutilで「ファイルとフォルダをまとめて扱う」感覚を身につける

Day 18では、標準ライブラリ shutil を使って、 ファイルコピー・移動・削除・フォルダコピーといった「実務でよく使う操作」を、初心者向けにかみ砕いて学んでいきます。 ospathlib と組み合わせることで、「バックアップツール」「整理ツール」などにすぐ応用できる力を育てることが目標です。

shutilとは何か ― 「まとめてファイル操作するための道具」

shutil は、ファイルやフォルダを「コピーする」「移動する」「削除する」といった操作を、 シンプルな関数でまとめて提供してくれる標準ライブラリです。

よく使う関数は次のようなものです。

  • shutil.copy():ファイルをコピーする
  • shutil.copy2():メタ情報(更新日時など)も含めてコピーする
  • shutil.move():ファイルやフォルダを移動する
  • shutil.rmtree():フォルダを中身ごと削除する
  • shutil.copytree():フォルダを丸ごとコピーする

これらを、pathlib.Path と組み合わせて使うと、コードが読みやすく・安全になります。

ファイルコピー:バックアップの基本操作

単純なファイルコピー(copy)

まずは、1つのファイルを別の場所にコピーする基本形から見ていきます。

import shutil
from pathlib import Path

def copy_file_example():
    """1つのファイルを別の場所にコピーする例です。"""
    src = Path("data/original.txt")   # コピー元ファイル
    dst = Path("backup/original_copy.txt")  # コピー先ファイル

    # コピー先フォルダがなければ作成しておきます
    dst.parent.mkdir(parents=True, exist_ok=True)

    # shutil.copy() は「中身」をコピーします(メタ情報は一部のみ)
    shutil.copy(src, dst)
    print("ファイルをコピーしました。")
    print("コピー元:", src)
    print("コピー先:", dst)
Python

ここでのポイントは、

  • Path でパスを扱い、dst.parent.mkdir() でコピー先フォルダを事前に作っていること
  • shutil.copy(src, dst) で「ファイルの中身」をコピーしていること

です。バックアップや一時コピーの基本として、まずこの形を押さえておくと安心です。

メタ情報も含めてコピーする(copy2)

更新日時などのメタ情報も含めてコピーしたい場合は、copy2() を使います。

import shutil
from pathlib import Path

def copy_file_with_metadata_example():
    """メタ情報も含めてファイルをコピーする例です。"""
    src = Path("data/report.txt")
    dst = Path("backup/report_copy.txt")

    dst.parent.mkdir(parents=True, exist_ok=True)

    # copy2() は、可能な限りメタ情報もコピーします
    shutil.copy2(src, dst)
    print("ファイル(メタ情報付き)をコピーしました。")
    print("コピー元:", src)
    print("コピー先:", dst)
Python

ログやレポートなど、「更新日時も含めて残しておきたい」ファイルのバックアップに向いています。

ファイル・フォルダの移動:整理やリネームに使う

ファイルを別フォルダへ移動する(move)

shutil.move() は、「コピー+元の削除」をまとめて行う関数です。 ファイルの整理や、フォルダ構成の変更に向いています。

import shutil
from pathlib import Path

def move_file_example():
    """ファイルを別フォルダへ移動する例です。"""
    src = Path("downloads/temp.txt")      # 移動元
    dst_dir = Path("documents")           # 移動先フォルダ
    dst_dir.mkdir(parents=True, exist_ok=True)

    dst = dst_dir / src.name  # 元のファイル名をそのまま使います

    shutil.move(src, dst)
    print("ファイルを移動しました。")
    print("移動元:", src)
    print("移動先:", dst)
Python

ここでのポイントは、

  • dst_dir / src.name で「元のファイル名を保ったまま移動先パスを作っている」こと
  • move() は「コピー+元の削除」なので、移動後に元の場所にはファイルが残らないこと

です。

ファイル名の変更(リネーム)にも使える

move() は「同じフォルダ内で別名に移動する」ことで、リネームにも使えます。

import shutil
from pathlib import Path

def rename_file_example():
    """ファイル名を変更する例です(moveを利用)。"""
    src = Path("data/old_name.txt")
    dst = Path("data/new_name.txt")

    shutil.move(src, dst)
    print("ファイル名を変更しました。")
    print("旧ファイル名:", src)
    print("新ファイル名:", dst)
Python

「リネーム専用関数」ではありませんが、実務ではこのように使うことがよくあります。

削除:フォルダを中身ごと消すときの注意点

フォルダを丸ごと削除する(rmtree)

shutil.rmtree() は、「フォルダを中身ごと削除する」強力な関数です。 便利ですが、誤って重要なフォルダを消さないように、慎重に使う必要があります。

import shutil
from pathlib import Path

def remove_directory_example():
    """フォルダを中身ごと削除する例です。"""
    target = Path("temp_data")

    if not target.exists():
        print("削除対象のフォルダが存在しません。")
        return

    # 本当に削除してよいか確認します
    answer = input(f"{target} を中身ごと削除します。よろしいですか? (y/n): ").strip().lower()
    if answer != "y":
        print("削除をキャンセルしました。")
        return

    shutil.rmtree(target)
    print("フォルダを中身ごと削除しました:", target)
Python

ここでの重要ポイントは、

  • rmtree() は「中身ごと」削除するため、取り消しが難しいこと
  • ユーザーに確認を取るなど、安全策を入れておくこと

です。Day 18では、「便利さ」と「危険さ」の両方を意識して使う感覚を育てていきます。

フォルダコピー:プロジェクト丸ごとバックアップする

copytree() でフォルダを丸ごとコピーする

shutil.copytree() は、「フォルダを中身ごと別の場所にコピーする」関数です。 プロジェクトのバックアップや、テンプレートフォルダの複製などに向いています。

import shutil
from pathlib import Path

def copy_directory_example():
    """フォルダを丸ごとコピーする例です。"""
    src = Path("project")          # コピー元フォルダ
    dst = Path("project_backup")   # コピー先フォルダ

    if not src.exists():
        print("コピー元フォルダが存在しません。")
        return

    if dst.exists():
        print("コピー先フォルダがすでに存在しています。別の名前を指定してください。")
        return

    shutil.copytree(src, dst)
    print("フォルダを丸ごとコピーしました。")
    print("コピー元:", src)
    print("コピー先:", dst)
Python

ここでのポイントは、

  • copytree() は「コピー先フォルダが存在しているとエラーになる」ため、事前チェックが必要なこと
  • フォルダ配下のファイル・サブフォルダをすべてコピーしてくれること

です。プロジェクトのスナップショットを取るときなどに非常に便利です。

Day 18ミニテンプレート:簡易バックアップ&整理ツール

最後に、Day 18で学んだ shutil の機能をまとめて試せる「簡易バックアップ&整理ツール」のテンプレートを示します。

# day18_shutil_tool.py
# shutilを使ったファイル・フォルダ操作の練習ツール

import shutil
from pathlib import Path


def choose_path(prompt: str) -> Path | None:
    """ユーザーにパスを入力してもらい、Pathとして返す簡易関数です。"""
    text = input(prompt).strip()
    if text == "":
        print("パスが空です。")
        return None
    p = Path(text)
    return p


def copy_file():
    src = choose_path("コピー元ファイルのパスを入力してください: ")
    if src is None or not src.is_file():
        print("コピー元がファイルではありません。")
        return

    dst = choose_path("コピー先ファイルのパスを入力してください: ")
    if dst is None:
        return

    dst.parent.mkdir(parents=True, exist_ok=True)
    shutil.copy2(src, dst)
    print("ファイルをコピーしました。")
    print("コピー元:", src)
    print("コピー先:", dst)


def move_file():
    src = choose_path("移動元ファイルのパスを入力してください: ")
    if src is None or not src.is_file():
        print("移動元がファイルではありません。")
        return

    dst = choose_path("移動先ファイルのパスを入力してください: ")
    if dst is None:
        return

    dst.parent.mkdir(parents=True, exist_ok=True)
    shutil.move(src, dst)
    print("ファイルを移動しました。")
    print("移動元:", src)
    print("移動先:", dst)


def remove_directory():
    target = choose_path("削除したいフォルダのパスを入力してください: ")
    if target is None or not target.is_dir():
        print("削除対象がフォルダではありません。")
        return

    answer = input(f"{target} を中身ごと削除します。よろしいですか? (y/n): ").strip().lower()
    if answer != "y":
        print("削除をキャンセルしました。")
        return

    shutil.rmtree(target)
    print("フォルダを中身ごと削除しました:", target)


def copy_directory():
    src = choose_path("コピー元フォルダのパスを入力してください: ")
    if src is None or not src.is_dir():
        print("コピー元がフォルダではありません。")
        return

    dst = choose_path("コピー先フォルダのパスを入力してください: ")
    if dst is None:
        return

    if dst.exists():
        print("コピー先フォルダがすでに存在しています。別の名前を指定してください。")
        return

    shutil.copytree(src, dst)
    print("フォルダを丸ごとコピーしました。")
    print("コピー元:", src)
    print("コピー先:", dst)


def main():
    while True:
        print("\n=== Day 18:shutil 練習ツール ===")
        print("1: ファイルをコピーする")
        print("2: ファイルを移動する")
        print("3: フォルダを中身ごと削除する")
        print("4: フォルダを丸ごとコピーする")
        print("5: 終了する")

        choice = input("番号を入力してください: ").strip()

        if choice == "1":
            copy_file()
        elif choice == "2":
            move_file()
        elif choice == "3":
            remove_directory()
        elif choice == "4":
            copy_directory()
        elif choice == "5":
            print("shutil 練習ツールを終了します。")
            break
        else:
            print("1〜5の番号を入力してください。")


if __name__ == "__main__":
    main()
Python

Day 18のまとめ

Day 18では、shutil を通して、

  • copy()copy2() によるファイルコピー(バックアップの基本)
  • move() によるファイル・フォルダの移動やリネーム
  • rmtree() によるフォルダの中身ごとの削除(確認付きで安全に使う感覚)
  • copytree() によるフォルダ丸ごとコピー(プロジェクトのスナップショット)

をステップバイステップで体験していただきました。

ここまで来ると、「Pythonでファイルやフォルダを整理したり、バックアップしたりするツールを自分で作れる」という感覚が少し育っているはずです。 この感覚は、今後の実践Python編で自動化スクリプトや運用ツールを作っていくうえで、非常に心強い土台になっていきます。


Day 18:shutilで「自動バックアップツール」を作る

Day 18の制作編では、標準ライブラリ shutil を使って、 ファイルコピー・移動・削除・フォルダコピーを組み合わせた「自動バックアップツール」を作っていきます。 プログラミング初心者の方でも、実務で使えるレベルのバックアップ処理の流れをイメージできるよう、ステップバイステップで解説します。

自動バックアップツールの全体像をイメージする

どんなことをするツールか

今回作る自動バックアップツールは、次のような流れで動くことを目標にします。

  • バックアップ元フォルダバックアップ先フォルダを指定する
  • バックアップ先に「日付入りのフォルダ」を作り、そこへコピーする
  • 古いバックアップフォルダを一定数以上残さないように削除する(例:最新3世代だけ残す)
  • 必要に応じて、古い一時ファイルを移動・削除する

この中で、

  • ファイルコピーshutil.copy2()
  • フォルダコピーshutil.copytree()
  • 移動shutil.move()
  • 削除shutil.rmtree()

を自然に使っていきます。

バックアップ元・先フォルダを指定する

pathlibでパスを扱う

まずは、ユーザーに「バックアップ元」と「バックアップ先」を入力してもらい、Path として扱えるようにします。

from pathlib import Path

def choose_directory(prompt: str) -> Path | None:
    """フォルダパスを入力してもらい、Pathとして返す関数です。"""
    text = input(prompt).strip()
    if text == "":
        print("パスが空です。")
        return None

    directory = Path(text)

    if not directory.exists():
        print("指定されたフォルダは存在しません。")
        return None

    if not directory.is_dir():
        print("指定されたパスはフォルダではありません。")
        return None

    return directory
Python

この関数を使って、バックアップ元・先を取得します。

def get_backup_paths():
    """バックアップ元とバックアップ先のフォルダを取得する関数です。"""
    src = choose_directory("バックアップ元フォルダのパスを入力してください: ")
    if src is None:
        return None, None

    dst = choose_directory("バックアップ先フォルダのパスを入力してください: ")
    if dst is None:
        return None, None

    return src, dst
Python

日付入りのバックアップフォルダを作る

datetimeで「バックアップ名」を作る

バックアップ先フォルダの中に、「日付入りのサブフォルダ」を作り、その中にコピーしていきます。

from datetime import datetime
from pathlib import Path

def create_backup_folder(base_dst: Path) -> Path:
    """バックアップ先フォルダの中に、日付入りのバックアップフォルダを作成する関数です。"""
    base_dst.mkdir(parents=True, exist_ok=True)

    # 例:backup_2026-08-30_09-30-00 のようなフォルダ名を作ります
    timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
    backup_dir = base_dst / f"backup_{timestamp}"

    backup_dir.mkdir()
    print("バックアップフォルダを作成しました:", backup_dir)

    return backup_dir
Python

ここでのポイントは、

  • datetime.now().strftime() で「人間にも分かりやすい日付文字列」を作っていること
  • base_dst / f"backup_{timestamp}" で「バックアップ先配下にサブフォルダ」を作っていること

です。これにより、「いつのバックアップか」がフォルダ名から一目で分かるようになります。

フォルダ丸ごとコピー:copytreeでバックアップを取る

shutil.copytree() を使ったバックアップ

バックアップ元フォルダを、バックアップフォルダに丸ごとコピーします。

import shutil
from pathlib import Path

def backup_directory(src: Path, backup_dir: Path):
    """バックアップ元フォルダを、バックアップフォルダに丸ごとコピーする関数です。"""
    # backup_dirの中に、元フォルダ名をそのまま使ったサブフォルダを作ります
    dst = backup_dir / src.name

    if dst.exists():
        print("バックアップ先に同名フォルダがすでに存在しています。")
        return

    shutil.copytree(src, dst)
    print("フォルダを丸ごとバックアップしました。")
    print("バックアップ元:", src)
    print("バックアップ先:", dst)
Python

ここでの重要ポイントは、

  • copytree() は「コピー先フォルダが存在しているとエラーになる」ため、事前に dst.exists() をチェックしていること
  • backup_dir / src.name で「元フォルダ名を保ったままバックアップ先にコピー」していること

です。これで、「project」フォルダを「backup_日付/project」として保存する形になります。

古いバックアップを削除する:世代管理

最新N世代だけ残すロジック

バックアップを取り続けると、バックアップ先フォルダがどんどん増えていきます。 そこで、「最新3世代だけ残し、それより古いバックアップフォルダは削除する」処理を入れます。

from pathlib import Path

def cleanup_old_backups(base_dst: Path, keep: int = 3):
    """バックアップ先フォルダ内の古いバックアップを削除する関数です。"""
    if not base_dst.exists():
        return

    # backup_ で始まるフォルダを一覧します
    backup_dirs = [d for d in base_dst.iterdir() if d.is_dir() and d.name.startswith("backup_")]

    # フォルダ名でソート(名前に日付が入っている前提)
    backup_dirs.sort()

    # 残すべきフォルダ数より多い場合、古いものから削除します
    if len(backup_dirs) <= keep:
        print("削除する古いバックアップはありません。")
        return

    to_delete = backup_dirs[:-keep]  # 古いもの(先頭側)を削除対象にします

    for d in to_delete:
        answer = input(f"古いバックアップ {d} を削除します。よろしいですか? (y/n): ").strip().lower()
        if answer == "y":
            shutil.rmtree(d)
            print("削除しました:", d)
        else:
            print("削除をスキップしました:", d)
Python

ここでの重要ポイントは、

  • バックアップフォルダ名を backup_YYYY-MM-DD_... としているため、「名前でソートすると古い順になる」こと
  • backup_dirs[:-keep] で「残すべき数を除いた古いフォルダ」を削除対象にしていること
  • rmtree() を使う前に、ユーザーに確認を取っていること

です。これにより、「バックアップは自動で増えるが、古いものは適度に整理される」状態を保てます。

一時ファイルの移動・削除(オプション機能)

一時ファイルを別フォルダへ移動する

バックアップ前に、「一時ファイル(例:.tmp)」を別フォルダへ移動しておきたい場合の例です。

import shutil
from pathlib import Path

def move_temp_files(src: Path):
    """バックアップ元フォルダ内の .tmp ファイルを temp フォルダへ移動する例です。"""
    temp_dir = src / "temp"
    temp_dir.mkdir(parents=True, exist_ok=True)

    moved_count = 0

    for file in src.rglob("*.tmp"):
        if file.is_file():
            dst = temp_dir / file.name
            shutil.move(file, dst)
            moved_count += 1

    print(f".tmp ファイルを {moved_count} 個、temp フォルダへ移動しました。")
Python

一時ファイルを削除する

移動ではなく削除したい場合は、unlink() を使います。

from pathlib import Path

def delete_temp_files(src: Path):
    """バックアップ元フォルダ内の .tmp ファイルを削除する例です。"""
    deleted_count = 0

    for file in src.rglob("*.tmp"):
        if file.is_file():
            answer = input(f"一時ファイル {file} を削除しますか? (y/n): ").strip().lower()
            if answer == "y":
                file.unlink()
                deleted_count += 1

    print(f".tmp ファイルを {deleted_count} 個削除しました。")
Python

ここでは、

  • 「移動」と「削除」を分けて考えることで、安全性を確保していること
  • rglob("*.tmp") で「サブフォルダも含めて一時ファイルを検索」していること

がポイントです。

自動バックアップツールの全体テンプレート

ここまでの関数を組み合わせて、「自動バックアップツール」として動く全体コードをまとめます。

# day18_auto_backup_tool.py
# shutilを使った自動バックアップツール

import shutil
from pathlib import Path
from datetime import datetime


def choose_directory(prompt: str) -> Path | None:
    text = input(prompt).strip()
    if text == "":
        print("パスが空です。")
        return None

    directory = Path(text)

    if not directory.exists():
        print("指定されたフォルダは存在しません。")
        return None

    if not directory.is_dir():
        print("指定されたパスはフォルダではありません。")
        return None

    return directory


def get_backup_paths():
    src = choose_directory("バックアップ元フォルダのパスを入力してください: ")
    if src is None:
        return None, None

    dst = choose_directory("バックアップ先フォルダのパスを入力してください: ")
    if dst is None:
        return None, None

    return src, dst


def create_backup_folder(base_dst: Path) -> Path:
    base_dst.mkdir(parents=True, exist_ok=True)
    timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
    backup_dir = base_dst / f"backup_{timestamp}"
    backup_dir.mkdir()
    print("バックアップフォルダを作成しました:", backup_dir)
    return backup_dir


def backup_directory(src: Path, backup_dir: Path):
    dst = backup_dir / src.name

    if dst.exists():
        print("バックアップ先に同名フォルダがすでに存在しています。")
        return

    shutil.copytree(src, dst)
    print("フォルダを丸ごとバックアップしました。")
    print("バックアップ元:", src)
    print("バックアップ先:", dst)


def cleanup_old_backups(base_dst: Path, keep: int = 3):
    if not base_dst.exists():
        return

    backup_dirs = [d for d in base_dst.iterdir() if d.is_dir() and d.name.startswith("backup_")]
    backup_dirs.sort()

    if len(backup_dirs) <= keep:
        print("削除する古いバックアップはありません。")
        return

    to_delete = backup_dirs[:-keep]

    for d in to_delete:
        answer = input(f"古いバックアップ {d} を削除します。よろしいですか? (y/n): ").strip().lower()
        if answer == "y":
            shutil.rmtree(d)
            print("削除しました:", d)
        else:
            print("削除をスキップしました:", d)


def move_temp_files(src: Path):
    temp_dir = src / "temp"
    temp_dir.mkdir(parents=True, exist_ok=True)

    moved_count = 0

    for file in src.rglob("*.tmp"):
        if file.is_file():
            dst = temp_dir / file.name
            shutil.move(file, dst)
            moved_count += 1

    print(f".tmp ファイルを {moved_count} 個、temp フォルダへ移動しました。")


def main():
    src, dst = get_backup_paths()
    if src is None or dst is None:
        return

    while True:
        print("\n=== 自動バックアップツール(Day 18) ===")
        print(f"バックアップ元: {src}")
        print(f"バックアップ先: {dst}")
        print("1: 一時ファイル(*.tmp)を temp フォルダへ移動する")
        print("2: バックアップを実行する(フォルダ丸ごとコピー)")
        print("3: 古いバックアップを整理する(最新3世代だけ残す)")
        print("4: 終了する")

        choice = input("番号を入力してください: ").strip()

        if choice == "1":
            move_temp_files(src)
        elif choice == "2":
            backup_dir = create_backup_folder(dst)
            backup_directory(src, backup_dir)
        elif choice == "3":
            cleanup_old_backups(dst, keep=3)
        elif choice == "4":
            print("自動バックアップツールを終了します。")
            break
        else:
            print("1〜4の番号を入力してください。")


if __name__ == "__main__":
    main()
Python

Day 18(制作編)のまとめ

Day 18の制作編では、「自動バックアップツール」を通して、

  • shutil.copytree() によるフォルダ丸ごとバックアップ
  • shutil.rmtree() による古いバックアップフォルダの削除(確認付き)
  • shutil.move() による一時ファイルの整理
  • pathlibdatetime を組み合わせた「日付入りバックアップフォルダ」の設計

を一つのプログラムの中で体験していただきました。

ここまで来ると、「Pythonで自分用のバックアップ・整理ツールを作れる」という感覚が少し育っているはずです。 この感覚は、今後の実践Python編で、より高度な自動化スクリプトや運用ツールを作っていくうえで、非常に心強い土台になっていきます。

タイトルとURLをコピーしました