Python | ファイル・OS 操作:shutil.rmtree

Python Python
スポンサーリンク

概要(shutil.rmtree は「ディレクトリ丸ごと削除」を一発で行う強力な関数)

shutil.rmtree は、ディレクトリとその中身(ファイル・サブディレクトリ)を再帰的にすべて削除します。空でなくても消せるため便利ですが、誤指定すると取り返しがつきません。最重要ポイントは「削除対象の検証」「権限・読み取り専用の扱い」「失敗時のハンドリング」の3つ。pathlib と組み合わせて安全確認を徹底し、必要なら onerror で復旧してから削除します。

基本の使い方(ここが重要)

ディレクトリツリーを丸ごと削除する

import shutil

shutil.rmtree("build")  # build/ 配下をすべて削除
Python

指定パスがディレクトリで、その中身がどれだけあっても削除されます。ファイル単体の削除は rmtree ではなく Path.unlink を使います。

pathlib と併用して「存在・種別」を確認する

from pathlib import Path
import shutil

target = Path("dist")
if target.exists() and target.is_dir():
    shutil.rmtree(target)
Python

存在はするがディレクトリでないケース(同名ファイル)を事前に弾くと事故が減ります。

重要ポイントの深掘り(安全性・権限・失敗時の復旧)

削除対象の安全確認(ベース配下か、危険パスでないか)

from pathlib import Path

def ensure_under(base: Path, p: Path) -> Path:
    base = base.resolve(); p = p.resolve()
    if p == base or base in p.parents:
        return p
    raise ValueError("ベース外の削除は不可")

base = Path.cwd()
target = ensure_under(base, Path("build/artifacts"))
Python

誤ってホームやシステム領域を指定しないよう、「ベース配下のみ許可」などのガードを入れます。

読み取り専用(Windows)の削除失敗を onerror で解決

読み取り専用属性で PermissionError になるときは、onerror コールバックで属性変更後に再試行します。

import os, stat, shutil

def make_writable(func, path, exc_info):
    # path を書き込み可能にして再試行
    os.chmod(path, stat.S_IWRITE)
    func(path)

shutil.rmtree("build", onerror=make_writable)
Python

ignore_errors=True は「失敗を無視」して進みますが、片付けが中途半端になり得るため、基本は onerror で対処する方が安全です。

「空ディレクトリだけ」なら rmdir(対比理解)

from pathlib import Path

Path("empty_dir").rmdir()  # 空なら削除、空でないと OSError
Python

rmtree は中身ごと、rmdir は空だけ。用途に応じて使い分けます。

実務での使いどころ(クリーンアップ・再生成・一括掃除)

ビルド成果物のクリーンアップ(削除→再作成)

from pathlib import Path
import shutil

out = Path("build")
if out.exists():
    shutil.rmtree(out)
out.mkdir(parents=True, exist_ok=True)
Python

毎回クリーンな出力先を用意できます。

テンポラリの掃除(存在チェック+失敗時メッセージ)

from pathlib import Path
import shutil

tmp = Path("tmp/session_001")
try:
    if tmp.exists():
        shutil.rmtree(tmp)
except PermissionError:
    print("権限不足で削除できません:", tmp)
Python

運用では例外を捕まえてログを残すだけでも調査がしやすくなります。

glob と併用して複数フォルダを一括削除

from pathlib import Path
import shutil

for d in Path("logs").glob("old_*"):
    if d.is_dir():
        shutil.rmtree(d)
Python

パターンで対象を選び、ディレクトリのみ削除します。必要なら sorted で順序を固定します。

よくある落とし穴の回避(誤指定・権限・巨大ツリー)

誤指定防止(表示確認・ドライラン)

大規模削除の前に「対象一覧を表示」して確認する習慣をつけます。ドライランを用意するとより安全です。

from pathlib import Path
import shutil

def dry_run_rmtree(p: Path):
    print("削除対象:", p.resolve())
    for q in p.rglob("*"):
        print(" -", q)

target = Path("build")
dry_run_rmtree(target)
# 実行フェーズ
shutil.rmtree(target)
Python

権限・ロックで削除できない時の方針

  • 読み取り専用属性は onerror で解除して再試行。
  • 別プロセスがファイルを掴んでいる場合は、プロセス終了後に再試行する設計を検討。

巨大ツリーの削除時間と途中失敗

大量ファイルの削除は時間がかかります。途中で失敗した場合に「残骸があっても次回掃除できる」よう、再実行可能なスクリプトにしておくと安心です。

例題で身につける(定番から一歩先まで)

例題1:安全ガード付き rmtree

from pathlib import Path
import shutil

def safe_rmtree(p: Path, base: Path):
    p = p.resolve(); base = base.resolve()
    if p == base or base in p.parents:
        if p.exists() and p.is_dir():
            shutil.rmtree(p)
    else:
        raise ValueError(f"危険な削除要求: {p}")

safe_rmtree(Path("build/artifacts"), Path.cwd())
Python

例題2:Windows 読み取り専用対応 onerror

import os, stat, shutil

def onerror(func, path, exc_info):
    try:
        os.chmod(path, stat.S_IWRITE)
        func(path)
    except Exception:
        raise

shutil.rmtree("build", onerror=onerror)
Python

例題3:rmtree と rmdir の違いを確認

from pathlib import Path
import shutil

Path("work/a/b").mkdir(parents=True, exist_ok=True)
try:
    Path("work/a").rmdir()   # 中身があるので失敗
except OSError:
    print("rmdir は空のみ")

shutil.rmtree("work")        # ツリーごと削除
Python

まとめ

shutil.rmtree は「空でなくてもディレクトリを丸ごと消せる」強力な削除関数です。誤指定を防ぐためにベース配下チェックやドライランを入れ、pathlib で存在・種別を検証。読み取り専用での失敗は onerror で属性を直して再試行し、ignore_errors の安易な使用は避ける。巨大ツリーや実運用では、例外ハンドリングと再実行可能な設計をセットにしておくと、短く安全で頼れるクリーンアップ処理になります。

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