Python shutil活用 – ディレクトリ操作 (コピー・削除) とディスク情報

Pythonの shutil モジュールを使えば、ファイル単位だけでなく、ディレクトリ(フォルダ)全体を扱ったり、ディスクの空き容量を調べたりすることも可能です。

前回の記事ではファイルのコピーと移動を中心に解説しましたが、今回はディレクトリ操作と情報取得に焦点を当てて shutil の活用法を掘り下げます。

この記事では、

  • ディレクトリを丸ごとコピーする copytree()
  • 中身ごとディレクトリを削除する rmtree()
  • ディスクの使用状況を確認する disk_usage()
  • コマンドの場所を探す which()

について、具体的な使い方と注意点を解説します。

特に rmtree() は非常に便利な反面、使い方を誤ると危険なため、その注意点もしっかりお伝えします。この記事を読めば、shutil を使ったより高度なファイルシステム操作が可能になります。

スポンサーリンク

shutilモジュール別使い方

ディレクトリを丸ごとコピーする – shutil.copytree()

プロジェクトのバックアップや、テンプレートとなるディレクトリ構成の複製など、ディレクトリとその中身(サブディレクトリやファイル)を全てコピーしたい場面があります。

そんな時に活躍するのが shutil.copytree() です。

  • shutil.copytree(src, dst, symlinks=False, ignore=None, copy_function=copy2, ignore_dangling_symlinks=False, dirs_exist_ok=False):
    • src: コピー元のディレクトリパス。
    • dst: コピー先のディレクトリパス。重要な注意点として、デフォルトでは dst ディレクトリは存在していてはいけませんcopytree()dst ディレクトリ自体を作成します。 (ただし、Python 3.8以降では dirs_exist_ok=True を指定すると、既存のディレクトリにマージできます)
    • copy_function: 内部でファイルのコピーに使われる関数を指定します。デフォルトは copy2 なので、メタデータも保持されます。copy を指定すればメタデータは保持されません。
    • その他の引数 (symlinks, ignore など) で、シンボリックリンクの扱いを指定したり、特定のファイルやパターンを無視したりすることも可能です(詳細は公式ドキュメント参照)。

コード例:

Python

import shutil
import os

# 事前準備: コピー元のディレクトリ構造を作成
os.makedirs("project_template/src", exist_ok=True)
os.makedirs("project_template/docs", exist_ok=True)
with open("project_template/README.md", "w") as f:
    f.write("# Project Template")
with open("project_template/src/main.py", "w") as f:
    f.write("print('Hello')")

print("--- shutil.copytree() の実行 ---")
# 'project_template' を 'my_project' として丸ごとコピー
# コピー先 'my_project' は存在しない状態で実行する必要あり
destination_dir = "my_project"

if os.path.exists(destination_dir):
    print(f"エラー: コピー先 '{destination_dir}' が既に存在します。削除してから再試行してください。")
else:
    try:
        shutil.copytree("project_template", destination_dir)
        print(f"'{destination_dir}' にディレクトリツリーをコピーしました。")
        # コピーされたか確認
        print(f"コピー先のREADME存在確認: {os.path.exists(os.path.join(destination_dir, 'README.md'))}")
        print(f"コピー先のmain.py存在確認: {os.path.exists(os.path.join(destination_dir, 'src/main.py'))}")
    except FileExistsError:
        # 基本的に↑のif分岐で防げるはずだが、念のため
        print(f"エラー: コピー先 '{destination_dir}' が既に存在します。")
    except Exception as e:
        print(f"copytree エラー: {e}")

# Python 3.8以降: dirs_exist_ok=True の例
# if os.path.exists(destination_dir): # わざと存在させる
#     try:
#         # 既存のディレクトリにファイルを追加・上書きする形でコピー
#         with open("project_template/new_file.txt", "w") as f: f.write("new") # 元にないファイル追加
#         shutil.copytree("project_template", destination_dir, dirs_exist_ok=True)
#         print(f"'{destination_dir}' にディレクトリツリーをマージしました (dirs_exist_ok=True)。")
#         os.remove("project_template/new_file.txt") # 後片付け
#     except Exception as e:
#         print(f"copytree (dirs_exist_ok=True) エラー: {e}")


# 後片付け (必要に応じてコメント解除)
# shutil.rmtree("project_template")
# shutil.rmtree(destination_dir)

ディレクトリを中身ごと削除する – shutil.rmtree()【要注意!】

一時ディレクトリのクリーンアップや、不要になったプロジェクトフォルダの削除など、ディレクトリとその中身を完全に削除したい場合に shutil.rmtree() を使います。

  • shutil.rmtree(path, ignore_errors=False, onerror=None):
    • path: 削除したいディレクトリのパス。
    • ignore_errors=True にすると、削除中にエラーが発生しても無視して処理を続行しようとします(ただし、完全に成功する保証はありません)。
    • onerror: エラー発生時に呼び出されるコールバック関数を指定できます。

非常に重要な注意点:

  • rmtree() は元に戻せません! 一度実行すると、指定したディレクトリとその中の全てのファイル、サブディレクトリが完全に消去されます
  • パスの指定は絶対に間違えないでください。 タイプミス一つで、システムに必要なディレクトリや大切なデータフォルダを消してしまう可能性があります。
  • 使用する際は、削除対象のパスが本当に正しいか、細心の注意を払って確認してください。
  • 可能であれば、削除前にユーザーに確認を促す処理を入れたり、テスト環境で十分に動作検証を行うことを強く推奨します。
  • os.rmdir() は空のディレクトリしか削除できませんが、rmtree() は中身があっても強制的に削除します。この違いを理解しておくことが重要です。

コード例:

Python

import shutil
import os

# 事前準備: 削除対象のディレクトリを作成
os.makedirs("temp_data/logs", exist_ok=True)
with open("temp_data/data.tmp", "w") as f:
    f.write("一時データ")
with open("temp_data/logs/app.log", "w") as f:
    f.write("ログ")

print("削除対象のディレクトリ 'temp_data' を作成しました。")
delete_target = "temp_data"

# !!! 最終確認 !!!
confirm = input(f"本当にディレクトリ '{delete_target}' とその中身を完全に削除しますか? (yes/no): ")

if confirm.lower() == 'yes':
    print(f"'{delete_target}' を削除します...")
    try:
        shutil.rmtree(delete_target)
        print(f"'{delete_target}' を正常に削除しました。")
    except FileNotFoundError:
        print(f"エラー: ディレクトリ '{delete_target}' が見つかりません。")
    except PermissionError:
        print(f"エラー: ディレクトリ '{delete_target}' を削除する権限がありません。")
    except Exception as e:
        print(f"rmtree エラー: {e}")
else:
    print("削除をキャンセルしました。")

# 削除されたか確認
if not os.path.exists(delete_target):
    print(f"'{delete_target}' は存在しません。")
else:
    print(f"'{delete_target}' は削除されませんでした。") # キャンセルした場合など

ディスク使用量を取得する – shutil.disk_usage()

プログラムの実行前にディスクの空き容量を確認したり、定期的にディスク使用量を監視したりする際に shutil.disk_usage() が役立ちます。

  • shutil.disk_usage(path): path が存在するディスクドライブ(またはマウントポイント)の合計容量 (total)、使用量 (used)、空き容量 (free) をバイト単位で取得し、名前付きタプル (total, used, free) として返します。

コード例:

Python

import shutil
import os

# カレントディレクトリがあるディスクの情報を取得
try:
    # Linux/macOSなら '/'、Windowsなら 'C:\\' など特定のドライブも指定可能
    usage = shutil.disk_usage(".")
    print("ディスク使用状況 (カレント):")
    # バイト単位だと大きいので、GB単位に変換して表示 (1 GB = 1024 * 1024 * 1024 bytes)
    gb = 1024**3
    print(f"  合計: {usage.total / gb:.2f} GB")
    print(f"  使用量: {usage.used / gb:.2f} GB")
    print(f"  空き容量: {usage.free / gb:.2f} GB")
    # 空き容量の割合
    free_percent = (usage.free / usage.total) * 100
    print(f"  空き容量 (%): {free_percent:.2f}%")
except FileNotFoundError:
    print("エラー: 指定されたパスが存在しません。")
except Exception as e:
    print(f"disk_usage エラー: {e}")

実行ファイルのパスを取得する – shutil.which()

システムに特定のコマンド(実行ファイル)がインストールされているか確認し、その場所(フルパス)を取得したい場合があります。

例えば、外部プログラムをPythonから呼び出す前に、そのプログラムが存在するかどうかをチェックするのに使えます。

  • shutil.which(cmd): 環境変数 PATH で指定されたディレクトリ群の中から、実行可能なファイル cmd を探します。見つかればそのフルパスを文字列として返し、見つからなければ None を返します。

コード例:

Python

import shutil

# 'git' コマンドがシステムに存在するか確認
git_path = shutil.which("git")
if git_path:
    print(f"'git' のパス: {git_path}")
else:
    print("'git' はシステムに見つかりません (PATH上にないか、インストールされていません)。")

# 'python' (または 'python3') コマンドの確認
python_path = shutil.which("python") or shutil.which("python3")
if python_path:
    print(f"Pythonインタープリターのパス: {python_path}")
else:
    print("Pythonインタープリターが見つかりません。")

# 存在しないであろうコマンド
non_existent = shutil.which("this_command_should_not_exist_anywhere")
if non_existent is None:
    print("存在しないコマンドは None を返します。")

スポンサーリンク

まとめ

今回は shutil モジュールを使ったディレクトリ操作 (copytree, rmtree) と、ディスク情報・実行パスの取得 (disk_usage, which) について解説しました。

  • ディレクトリのコピー
    • shutil.copytree() でサブディレクトリやファイルごとコピーできる。
    • デフォルトではコピー先ディレクトリは存在してはいけない点に注意。
  • ディレクトリの削除
    • shutil.rmtree() で中身ごと完全に削除できる。
    • 非常に強力なため、パスの確認を怠らず、細心の注意を払って使用すること!
  • ディスク情報の取得
    • shutil.disk_usage() で合計/使用量/空き容量がわかる。
  • 実行パスの取得
    • shutil.which() でコマンドが PATH 上にあるか確認できる。

これらの機能を活用することで、Pythonスクリプトから行えるファイルシステムの操作範囲が大きく広がります。特に copytreermtree は、ファイル整理やデプロイメントの自動化などで非常に役立ちますが、rmtree のリスクは常に意識してください。

次の記事【記事3/3】では、これらの shutil 操作をより安全かつ効果的に行うためのエラーハンドリング、pathlib との連携、その他の応用テクニックについて解説します。

スポンサーリンク

コメント

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