Pythonでファイル・ディレクトリをコピーする方法(shutil.copy, copytree)

 

Pythonでファイルやディレクトリをコピーする際に使用するshutilモジュールの関数について、実用的なサンプルコードと共に詳しく解説します。

ファイルコピーの基本

shutil.copy() – 基本的なファイルコピー

import shutil

# ファイルをコピー(メタデータは保持されない)
shutil.copy('source.txt', 'destination.txt')

shutil.copy2() – メタデータを保持するコピー

import shutil

# ファイルをコピー(タイムスタンプなどのメタデータも保持)
shutil.copy2('source.txt', 'backup/source.txt')

shutil.copyfile() – ファイル内容のみコピー

import shutil

# ファイル内容のみコピー(権限情報は引き継がれない)
shutil.copyfile('source.txt', 'copy_only_content.txt')

ディレクトリのコピー

shutil.copytree() – ディレクトリ全体のコピー

import shutil

# ディレクトリとその中身を再帰的にコピー
shutil.copytree('source_folder', 'destination_folder')

既存ディレクトリへのコピー(Python 3.8以降)

import shutil

# 既存のディレクトリにファイルを追加コピー
shutil.copytree('source', 'existing_dest', dirs_exist_ok=True)

エラーハンドリングを含む安全なコピー

基本的なエラーハンドリング

import shutil
import os

def safe_copy(src, dst):
    try:
        if not os.path.exists(src):
            return f"エラー: '{src}' が存在しません"
        
        # 移動先ディレクトリを作成
        dst_dir = os.path.dirname(dst)
        if dst_dir and not os.path.exists(dst_dir):
            os.makedirs(dst_dir)
        
        if os.path.isfile(src):
            shutil.copy2(src, dst)
            return f"ファイルコピー完了: '{dst}'"
        elif os.path.isdir(src):
            shutil.copytree(src, dst, dirs_exist_ok=True)
            return f"ディレクトリコピー完了: '{dst}'"
    
    except PermissionError:
        return "エラー: 権限がありません"
    except shutil.Error as e:
        return f"コピーエラー: {e}"
    except Exception as e:
        return f"予期しないエラー: {e}"

# 使用例
print(safe_copy('test.txt', 'backup/test.txt'))

同名ファイル対応のコピー

import os
import shutil

def copy_with_rename(src, dst):
    if os.path.exists(dst):
        base, ext = os.path.splitext(dst)
        counter = 1
        while os.path.exists(f"{base}_copy{counter}{ext}"):
            counter += 1
        dst = f"{base}_copy{counter}{ext}"
    
    shutil.copy2(src, dst)
    return dst

# 使用例
new_path = copy_with_rename('file.txt', 'backup/file.txt')
print(f"コピー先: {new_path}")

条件指定でのファイルコピー

ファイル拡張子による選択コピー

import os
import shutil
import glob

def copy_by_extension(src_dir, dst_dir, extensions):
    os.makedirs(dst_dir, exist_ok=True)
    copied_files = []
    
    for ext in extensions:
        pattern = os.path.join(src_dir, f"*.{ext}")
        files = glob.glob(pattern)
        
        for file in files:
            filename = os.path.basename(file)
            dst_path = os.path.join(dst_dir, filename)
            shutil.copy2(file, dst_path)
            copied_files.append(filename)
    
    return copied_files

# 使用例:txt, pdf ファイルのみコピー
copied = copy_by_extension('documents', 'backup', ['txt', 'pdf'])
print(f"コピーしたファイル: {copied}")

サイズ制限付きコピー

import os
import shutil

def copy_small_files(src_dir, dst_dir, max_size_mb=10):
    os.makedirs(dst_dir, exist_ok=True)
    max_bytes = max_size_mb * 1024 * 1024
    copied_count = 0
    
    for filename in os.listdir(src_dir):
        src_path = os.path.join(src_dir, filename)
        
        if os.path.isfile(src_path):
            if os.path.getsize(src_path) <= max_bytes:
                dst_path = os.path.join(dst_dir, filename)
                shutil.copy2(src_path, dst_path)
                copied_count += 1
    
    return copied_count

# 使用例:10MB以下のファイルのみコピー
count = copy_small_files('source', 'small_files_backup', 10)
print(f"{count}個のファイルをコピーしました")

高度なコピー操作

進捗表示付きコピー

import shutil
import os

def copy_with_progress(src, dst):
    if os.path.isfile(src):
        file_size = os.path.getsize(src)
        
        def progress_callback():
            print(".", end="", flush=True)
        
        print(f"コピー中: {src}")
        shutil.copy2(src, dst)
        print(f"\nコピー完了: {dst}")
    
    elif os.path.isdir(src):
        print(f"ディレクトリコピー中: {src}")
        
        def copy_function(src_file, dst_file):
            shutil.copy2(src_file, dst_file)
            print(".", end="", flush=True)
        
        shutil.copytree(src, dst, copy_function=copy_function)
        print(f"\nディレクトリコピー完了: {dst}")

# 使用例
copy_with_progress('large_folder', 'backup_folder')

選択的ディレクトリコピー

import shutil
import os

def selective_copytree(src, dst, ignore_patterns=None):
    ignore_patterns = ignore_patterns or []
    
    def ignore_function(directory, contents):
        ignored = []
        for item in contents:
            for pattern in ignore_patterns:
                if pattern in item:
                    ignored.append(item)
                    break
        return ignored
    
    shutil.copytree(src, dst, ignore=ignore_function)
    return f"選択的コピー完了: {dst}"

# 使用例:.pyc ファイルと __pycache__ を除外
result = selective_copytree(
    'project', 
    'project_backup',
    ignore_patterns=['.pyc', '__pycache__', '.git']
)
print(result)

差分コピー(新しいファイルのみ)

import os
import shutil

def incremental_copy(src_dir, dst_dir):
    os.makedirs(dst_dir, exist_ok=True)
    new_files = []
    updated_files = []
    
    for root, dirs, files in os.walk(src_dir):
        for file in files:
            src_path = os.path.join(root, file)
            rel_path = os.path.relpath(src_path, src_dir)
            dst_path = os.path.join(dst_dir, rel_path)
            
            # 移動先ディレクトリを作成
            os.makedirs(os.path.dirname(dst_path), exist_ok=True)
            
            if not os.path.exists(dst_path):
                shutil.copy2(src_path, dst_path)
                new_files.append(rel_path)
            elif os.path.getmtime(src_path) > os.path.getmtime(dst_path):
                shutil.copy2(src_path, dst_path)
                updated_files.append(rel_path)
    
    return {
        'new_files': new_files,
        'updated_files': updated_files
    }

# 使用例
result = incremental_copy('source', 'backup')
print(f"新規: {len(result['new_files'])}個")
print(f"更新: {len(result['updated_files'])}個")

コピー操作の検証

ハッシュ値による整合性チェック

import shutil
import hashlib
import os

def copy_and_verify(src, dst):
    def get_file_hash(filepath):
        with open(filepath, 'rb') as f:
            return hashlib.md5(f.read()).hexdigest()
    
    if os.path.isfile(src):
        original_hash = get_file_hash(src)
        shutil.copy2(src, dst)
        copied_hash = get_file_hash(dst)
        
        if original_hash == copied_hash:
            return f"コピー成功: ハッシュ値一致 ({dst})"
        else:
            return f"コピーエラー: ハッシュ値不一致"
    
    else:
        shutil.copytree(src, dst)
        return f"ディレクトリコピー完了: {dst}"

# 使用例(バイナリファイルで使用)
# result = copy_and_verify('image.jpg', 'backup/image.jpg')
# print(result)

ディスク容量チェック付きコピー

import shutil
import os

def copy_with_space_check(src, dst):
    def get_size(path):
        if os.path.isfile(path):
            return os.path.getsize(path)
        else:
            total = 0
            for dirpath, dirnames, filenames in os.walk(path):
                for filename in filenames:
                    fp = os.path.join(dirpath, filename)
                    total += os.path.getsize(fp)
            return total
    
    required_space = get_size(src)
    dst_dir = os.path.dirname(dst) if os.path.isfile(src) else dst
    available_space = shutil.disk_usage(dst_dir).free
    
    if required_space > available_space:
        return f"容量不足: 必要 {required_space}, 利用可能 {available_space}"
    
    if os.path.isfile(src):
        shutil.copy2(src, dst)
    else:
        shutil.copytree(src, dst)
    
    return f"コピー完了: {dst}"

# 使用例
print(copy_with_space_check('large_file.zip', 'backup/large_file.zip'))

バックアップシステムの実装

日付付きバックアップ

import shutil
import os
from datetime import datetime

def create_dated_backup(src, backup_root):
    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    
    if os.path.isfile(src):
        filename = os.path.basename(src)
        name, ext = os.path.splitext(filename)
        backup_name = f"{name}_{timestamp}{ext}"
        dst = os.path.join(backup_root, backup_name)
        shutil.copy2(src, dst)
    
    else:
        folder_name = os.path.basename(src.rstrip('/\\'))
        backup_name = f"{folder_name}_{timestamp}"
        dst = os.path.join(backup_root, backup_name)
        shutil.copytree(src, dst)
    
    return dst

# 使用例
backup_path = create_dated_backup('important.txt', 'backups')
print(f"バックアップ作成: {backup_path}")

まとめ

Pythonでのファイル・ディレクトリコピーには以下の関数を使い分けます:

  • shutil.copy(): 基本的なファイルコピー
  • shutil.copy2(): メタデータ保持ファイルコピー
  • shutil.copyfile(): ファイル内容のみコピー
  • shutil.copytree(): ディレクトリの再帰コピー

エラーハンドリング、進捗表示、選択的コピー機能を組み合わせることで、robust で使いやすいバックアップシステムを構築できます。

■プロンプトだけでオリジナルアプリを開発・公開してみた!!

■AI時代の第一歩!「AI駆動開発コース」はじめました!

テックジム東京本校で先行開始。

■テックジム東京本校

「武田塾」のプログラミング版といえば「テックジム」。
講義動画なし、教科書なし。「進捗管理とコーチング」で効率学習。
より早く、より安く、しかも対面型のプログラミングスクールです。

<短期講習>5日で5万円の「Pythonミニキャンプ」開催中。

<月1開催>放送作家による映像ディレクター養成講座

<オンライン無料>ゼロから始めるPython爆速講座