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

Python Python
スポンサーリンク

概要(shutil.move は「移動・リネーム」を1行で安全にやる標準関数)

shutil.move は、ファイル/ディレクトリを別の場所へ移動したり、名前を変更(リネーム)するための標準ライブラリの関数です。移動先が同じファイルシステムなら高速にリネームし、別ファイルシステムならコピー+元の削除まで面倒を見てくれます。重要なのは「移動先が“ディレクトリかファイル名か”で挙動が変わる」「親ディレクトリは先に作る」「上書きや衝突を自分で制御する」の3点です。

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

ファイルを別フォルダへ移す(フォルダ内にそのまま入る)

import shutil

# src を dst_dir 配下へ移動(ファイル名はそのまま)
shutil.move("downloads/report.pdf", "documents")
# → documents/report.pdf
Python

dst が既存のディレクトリなら「その中へ入る」動きになります。存在しない親フォルダは自動作成されないため、事前に作っておきます。

移動と同時に名前も変える(リネーム)

import shutil

# ファイル名を変えながら移動
shutil.move("downloads/old.txt", "archive/new_name.txt")
Python

dst が“ディレクトリでないパス”なら、その名前で移動(リネーム)します。

ディレクトリを丸ごと移動

import shutil

# ディレクトリごと移動(ツリー丸ごと)
shutil.move("data/raw", "data/processed/raw")
Python

丸ごと移動できますが、移動先に同名ディレクトリがあると衝突するため、事前に存在確認や削除/別名を検討します。

実務の安全設計(親作成・衝突回避・例外対応)

親ディレクトリは先に用意する(pathlib を併用)

from pathlib import Path
import shutil

dst = Path("archive/2025/report.csv")
dst.parent.mkdir(parents=True, exist_ok=True)
shutil.move("downloads/report.csv", dst)   # 文字列でも Path でもOK
Python

move は“親を作らない”ので、必ず先に mkdir(parents=True) で用意してから呼びます。

上書き・衝突を自分で制御する(安全なラッパの例)

from pathlib import Path
import shutil

def safe_move(src: Path, dst: Path, overwrite=False):
    src = Path(src); dst = Path(dst)
    dst.parent.mkdir(parents=True, exist_ok=True)
    if dst.exists():
        if not overwrite:
            raise FileExistsError(f"移動先が既に存在: {dst}")
        # 上書き許可なら、既存を消す/別名付与など方針を決める
        if dst.is_file():
            dst.unlink()
        else:
            # ディレクトリ既存なら別名へ退避などを検討
            raise IsADirectoryError(f"ディレクトリが既に存在: {dst}")
    shutil.move(str(src), str(dst))
Python

環境によっては既存ファイルがあると失敗/上書きになる挙動差があるため、明示的なポリシーで揃えます。

代表的な例外への備え(最低限のガード)

  • FileNotFoundError: 移動元が存在しない(パス間違い)
  • PermissionError: 権限不足(読み/書き/削除不可)
  • shutil.Error: 自己移動や不正な操作などの論理的エラー

発生し得る例外を想定し、ユーザー向けの分かりやすいメッセージと代替動作(別名、スキップ、ログ記録)を用意します。

よく使うパターン(一括移動・リネーム規則・ログ整理)

パターン一致でまとめて移動(glob と組み合わせ)

from pathlib import Path
import shutil

src_dir = Path("downloads")
dst_dir = Path("archive/docs")
dst_dir.mkdir(parents=True, exist_ok=True)

for f in src_dir.glob("*.pdf"):
    shutil.move(str(f), str(dst_dir / f.name))
Python

拡張子や接頭辞で選んで、一括移動します。取得順はファイルシステム依存なので、必要なら sorted で並べます。

規則に沿って“名前替え+移動”

from pathlib import Path
import shutil

src = Path("images")
dst = Path("images/renamed")
dst.mkdir(parents=True, exist_ok=True)

for i, f in enumerate(sorted(src.glob("*.png"), key=lambda p: p.stat().st_mtime), start=1):
    new = dst / f"{i:03d}_{f.stem}.png"
    shutil.move(str(f), str(new))
Python

「時刻順に連番+元名の一部」を付けるなど、規則を決めて整然と保存します。

“ログローテーション風”の安全移動

from pathlib import Path
import shutil

log = Path("logs/app.log")
rotated = log.with_name(f"app.log.1")

# 既存があればさらにずらすなどのポリシーも検討
if rotated.exists():
    rotated.unlink()
shutil.move(log, rotated)
Python

事前に既存ファイルの扱い(削除/連番ずらし)を決めてから move します。

深掘りポイント(os.rename との違い・同一/別FS・原子性)

os.rename との違い(基本はリネーム、move は“賢い”移動)

  • os.rename は同一ファイルシステム内のリネームが得意。別FSへの移動は失敗することがあります。
  • shutil.move は、同一FSならリネーム、別FSならコピー+元の削除を内部で切り替えます。これが「面倒を見てくれる」所以です。

同一/別ファイルシステムでの挙動

  • 同一FS: 高速・ほぼ即時に完了(メタデータの更新中心)
  • 別FS: 実際にデータコピーが走るため、サイズやディスク速度に比例して時間がかかる。失敗時のリカバリ(途中で止まる)の設計を考えると安心です。

“原子性”は用途による

move(特に別FS)では途中失敗により「元と先のどちらにもない」状態や「両方にある」状態が起き得ます。業務で重要なら、一時ファイル名へ移動→検証→本番名へ最終リネーム(同一FSで原子的)と段階化すると安全性が上がります。

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

例題1:親ディレクトリを用意してから安全移動

from pathlib import Path
import shutil

src = Path("downloads/report_2025.pdf")
dst = Path("documents/reports/report_2025.pdf")
dst.parent.mkdir(parents=True, exist_ok=True)
shutil.move(src, dst)
print("moved to:", dst)
Python

例題2:衝突を避けて“タイムスタンプ付”で移動

from pathlib import Path
from datetime import datetime
import shutil

src = Path("logs/app.log")
ts = datetime.now().strftime("%Y%m%d_%H%M%S")
dst = src.with_name(f"app_{ts}.log")
dst.parent.mkdir(parents=True, exist_ok=True)

if dst.exists():
    raise FileExistsError("意図外の衝突")
shutil.move(src, dst)
Python

例題3:拡張子ごとにサブフォルダへ分類移動

from pathlib import Path
import shutil

box = Path("inbox")
out = Path("sorted"); out.mkdir(parents=True, exist_ok=True)

groups = {".png": "images", ".csv": "tables", ".txt": "texts"}
for f in box.iterdir():
    if f.is_file():
        sub = groups.get(f.suffix.lower(), "others")
        target = out / sub / f.name
        target.parent.mkdir(parents=True, exist_ok=True)
        shutil.move(f, target)
Python

例題4:エラーを捕まえてメッセージを出す

import shutil
from pathlib import Path

try:
    shutil.move("missing.txt", "archive/missing.txt")
except FileNotFoundError:
    print("移動元がありません")
except PermissionError:
    print("権限不足です")
Python

まとめ

shutil.move は「移動」と「リネーム」を一発でこなす標準関数です。移動先がディレクトリかファイル名かで挙動が変わる点、親フォルダは先に作る点、上書き・衝突を自分で制御する点が最重要。別ファイルシステムではコピー+削除になるため時間がかかり、原子性は用途次第。pathlib と組み合わせて、結合→親作成→衝突ガード→移動の順に書けば、短くて安全なファイル整理・ログローテーション・一括分類が安定して動きます。

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