python的高级文件操作

什么是shutil?

shutil是Python标准库中的一个模块,名称来自"shell utilities"(shell工具)。它提供了许多高级的文件和目录操作功能,是os模块的增强版。
简单说:如果os模块是基本工具包,那么shutil就是电动工具套装!

为什么需要shutil?

对比:使用os模块 vs 使用shutil

# 场景:复制文件

# 1. 用os模块(麻烦)
import os
with open('source.txt', 'rb') as f1, open('dest.txt', 'wb') as f2:
    f2.write(f1.read())

# 2. 用shutil(简单)
import shutil
shutil.copy('source.txt', 'dest.txt')  # 一行搞定!

shutill的核心功能

文件复制操作

1.1 copy()- 复制文件

import shutil
import os

# 创建测试文件
with open('test.txt', 'w') as f:
    f.write("这是一个测试文件")

# 复制文件
shutil.copy('test.txt', 'test_copy.txt')
print("文件复制完成")

# 检查
if os.path.exists('test_copy.txt'):
    print("复制成功!")

1.2 copy2()- 保留元数据

import shutil
import os
import time

# 创建文件
with open('original.txt', 'w') as f:
    f.write("带有元数据的文件")

# 获取原始文件的修改时间
original_time = os.path.getmtime('original.txt')
print(f"原始文件修改时间:{time.ctime(original_time)}")

# 使用copy2复制(保留元数据)
shutil.copy2('original.txt', 'copy2_result.txt')

# 检查复制后的文件时间
copied_time = os.path.getmtime('copy2_result.txt')
print(f"复制文件修改时间:{time.ctime(copied_time)}")
print(f"时间是否相同:{original_time == copied_time}")

1.3 copyfile()- 只复制内容

import shutil

# copyfile只复制文件内容
shutil.copyfile('source.txt', 'destination.txt')

# 注意:copyfile不能复制目录
# 如果source.txt不存在会报错

目录复制

2.1 copytree()- 复制整个目录

import shutil
import os

# 创建测试目录结构
os.makedirs('source_dir/subdir1', exist_ok=True)
os.makedirs('source_dir/subdir2', exist_ok=True)

with open('source_dir/file1.txt', 'w') as f:
    f.write("文件1")
with open('source_dir/subdir1/file2.txt', 'w') as f:
    f.write("文件2")

# 复制整个目录
shutil.copytree('source_dir', 'destination_dir')
print("目录复制完成")

# 验证
print("目标目录内容:")
for root, dirs, files in os.walk('destination_dir'):
    for file in files:
        print(f"  {os.path.join(root, file)}")

2.2 忽略特定文件

import shutil

# 定义要忽略的文件/目录
def ignore_func(dirname, filenames):
    return ['__pycache__', '.git', '*.tmp']

# 复制时忽略指定文件
shutil.copytree('source', 'destination', ignore=ignore_func)

文件和目录移动/重命名

3.1 move()- 移动文件或目录

import shutil
import os

# 创建测试文件
with open('to_move.txt', 'w') as f:
    f.write("要移动的文件")

# 移动到新位置
shutil.move('to_move.txt', 'moved_files/to_move.txt')
print("文件移动完成")

# 如果目标目录不存在,会创建
shutil.move('another.txt', 'new_location/another.txt')

3.2 重命名功能

import shutil

# move也可以用来重命名
shutil.move('old_name.txt', 'new_name.txt')
print("文件重命名完成")

删除操作

4.1 rmtree()- 删除整个目录

import shutil
import os

# 创建测试目录
os.makedirs('to_delete_dir/subdir', exist_ok=True)
with open('to_delete_dir/file.txt', 'w') as f:
    f.write("测试")

# 删除整个目录(包括子目录和文件)
shutil.rmtree('to_delete_dir')
print("目录已删除")

# 注意:没有确认,直接删除!要小心使用

4.2 安全删除

import shutil
import os

def safe_rmtree(path):
    """安全删除目录"""
    if os.path.exists(path):
        confirm = input(f"确定要删除 {path} 吗?(y/n): ")
        if confirm.lower() == 'y':
            shutil.rmtree(path)
            print(f"已删除: {path}")
        else:
            print("取消删除")
    else:
        print(f"路径不存在: {path}")

# 使用
safe_rmtree('some_directory')

磁盘使用情况

5.1 disk_usage()- 检查磁盘空间

import shutil

# 获取磁盘使用情况
usage = shutil.disk_usage('/')  # Linux/Mac
# usage = shutil.disk_usage('C:\\')  # Windows

print(f"总空间: {usage.total / (1024**3):.2f} GB")
print(f"已用空间: {usage.used / (1024**3):.2f} GB")
print(f"可用空间: {usage.free / (1024**3):.2f} GB")
print(f"使用率: {usage.used / usage.total * 100:.1f}%")

shutil的压缩功能

make_archive()- 创建压缩包

import shutil
import os

# 创建测试文件
os.makedirs('data/files', exist_ok=True)
for i in range(3):
    with open(f'data/file{i}.txt', 'w') as f:
        f.write(f"这是文件{i}的内容")

# 创建不同类型的压缩包
# 1. ZIP格式
shutil.make_archive('my_zip_archive', 'zip', 'data')
print("ZIP压缩包创建完成")

# 2. TAR格式
shutil.make_archive('my_tar_archive', 'tar', 'data')
print("TAR压缩包创建完成")

# 3. GZIP压缩的TAR
shutil.make_archive('my_tar_gz_archive', 'gztar', 'data')
print("GZIP压缩的TAR包创建完成")

# 4. BZIP2压缩的TAR
shutil.make_archive('my_tar_bz2_archive', 'bztar', 'data')
print("BZIP2压缩的TAR包创建完成")

# 查看生成的文件
print("\n生成的压缩包:")
for file in os.listdir('.'):
    if file.startswith('my_') and (file.endswith('.zip') or file.endswith('.tar')):
        size = os.path.getsize(file) / 1024
        print(f"  {file}: {size:.1f} KB")

解压缩功能

import shutil

# 解压各种格式的压缩包
shutil.unpack_archive('my_zip_archive.zip', 'extracted_zip')
shutil.unpack_archive('my_tar_archive.tar', 'extracted_tar')
shutil.unpack_archive('my_tar_gz_archive.tar.gz', 'extracted_tar_gz')

print("解压完成")

实战应用

应用1:备份脚本

import shutil
import os
from datetime import datetime
import time

def backup_project(project_path, backup_dir, max_backups=5):
    """
    备份项目目录
    
    Args:
        project_path: 项目路径
        backup_dir: 备份目录
        max_backups: 最大保留备份数
    """
    if not os.path.exists(project_path):
        print(f"错误:项目路径不存在 {project_path}")
        return
    
    # 创建备份目录
    os.makedirs(backup_dir, exist_ok=True)
    
    # 生成备份文件名
    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    backup_name = f"backup_{timestamp}"
    backup_path = os.path.join(backup_dir, backup_name)
    
    # 复制项目
    print(f"开始备份 {project_path}...")
    start_time = time.time()
    
    try:
        shutil.copytree(project_path, backup_path)
        elapsed = time.time() - start_time
        print(f"备份完成: {backup_path} ({elapsed:.1f}秒)")
    except Exception as e:
        print(f"备份失败: {e}")
        return
    
    # 管理旧备份
    manage_old_backups(backup_dir, max_backups)

def manage_old_backups(backup_dir, max_backups):
    """管理旧的备份文件"""
    backups = []
    
    # 获取所有备份
    for item in os.listdir(backup_dir):
        item_path = os.path.join(backup_dir, item)
        if os.path.isdir(item_path) and item.startswith('backup_'):
            mtime = os.path.getmtime(item_path)
            backups.append((mtime, item_path, item))
    
    # 按时间排序
    backups.sort(reverse=True)  # 最新的在前
    
    # 删除旧的备份
    if len(backups) > max_backups:
        for mtime, path, name in backups[max_backups:]:
            print(f"删除旧备份: {name}")
            shutil.rmtree(path)

# 使用示例
backup_project('/path/to/my_project', '/path/to/backups', max_backups=7)

应用2:文件整理器

import shutil
import os
from pathlib import Path

def organize_files(source_dir, organize_by='ext'):
    """
    整理文件
    
    Args:
        source_dir: 源目录
        organize_by: 整理方式 ('ext'按扩展名, 'date'按日期, 'type'按类型)
    """
    source = Path(source_dir)
    
    if not source.exists():
        print(f"目录不存在: {source_dir}")
        return
    
    # 定义文件类型分类
    type_categories = {
        '图片': ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp'],
        '文档': ['.pdf', '.docx', '.doc', '.txt', '.md', '.rtf'],
        '压缩包': ['.zip', '.rar', '.7z', '.tar', '.gz', '.bz2'],
        '视频': ['.mp4', '.avi', '.mov', '.mkv', '.wmv'],
        '音频': ['.mp3', '.wav', '.flac', '.m4a'],
        '代码': ['.py', '.js', '.java', '.cpp', '.c', '.html', '.css']
    }
    
    moved_count = 0
    
    for item in source.iterdir():
        if item.is_file():
            if organize_by == 'ext':
                # 按扩展名分类
                ext = item.suffix.lower()
                if ext:
                    category = ext[1:].upper() + '_Files'  # 如 TXT_Files
                else:
                    category = 'NoExtension'
                    
            elif organize_by == 'type':
                # 按文件类型分类
                ext = item.suffix.lower()
                category = '其他文件'
                for cat, exts in type_categories.items():
                    if ext in exts:
                        category = cat
                        break
            else:
                print("不支持的整理方式")
                return
            
            # 创建目标目录
            target_dir = source / category
            target_dir.mkdir(exist_ok=True)
            
            # 移动文件
            try:
                shutil.move(str(item), str(target_dir / item.name))
                moved_count += 1
                print(f"移动: {item.name} -> {category}/")
            except Exception as e:
                print(f"移动失败 {item.name}: {e}")
    
    print(f"\n整理完成!移动了 {moved_count} 个文件")

# 使用示例
organize_files('/path/to/downloads', organize_by='type')

应用3:磁盘清理工具

import shutil
import os
import time
from datetime import datetime, timedelta

def disk_cleaner(directory, days_old=30, extensions=None, max_size_mb=100):
    """
    磁盘清理工具
    
    Args:
        directory: 要清理的目录
        days_old: 删除多少天前的文件
        extensions: 要处理的扩展名列表
        max_size_mb: 最大文件大小(MB),超过此大小的文件会被处理
    """
    if extensions is None:
        extensions = ['.tmp', '.log', '.cache', '.bak']
    
    cutoff_date = datetime.now() - timedelta(days=days_old)
    cutoff_timestamp = cutoff_date.timestamp()
    
    total_size = 0
    deleted_count = 0
    
    print(f"清理目录: {directory}")
    print(f"删除 {days_old} 天前的文件")
    print("-" * 50)
    
    for root, dirs, files in os.walk(directory):
        for file in files:
            file_path = os.path.join(root, file)
            
            # 检查扩展名
            if extensions and not any(file.endswith(ext) for ext in extensions):
                continue
            
            try:
                # 获取文件信息
                stat = os.stat(file_path)
                file_mtime = stat.st_mtime
                file_size = stat.st_size
                file_size_mb = file_size / (1024 * 1024)
                
                should_delete = False
                reason = ""
                
                # 检查是否满足删除条件
                if file_mtime < cutoff_timestamp:
                    should_delete = True
                    reason = f"超过{days_old}天"
                elif file_size_mb > max_size_mb:
                    should_delete = True
                    reason = f"大小超过{max_size_mb}MB"
                
                if should_delete:
                    # 删除文件
                    os.remove(file_path)
                    total_size += file_size
                    deleted_count += 1
                    
                    print(f"[{reason}] 删除: {file_path} "
                          f"({file_size_mb:.1f}MB, "
                          f"修改于{datetime.fromtimestamp(file_mtime).strftime('%Y-%m-%d')})")
                    
            except Exception as e:
                print(f"处理失败 {file_path}: {e}")
    
    # 删除空目录
    for root, dirs, files in os.walk(directory, topdown=False):
        for dir_name in dirs:
            dir_path = os.path.join(root, dir_name)
            try:
                if not os.listdir(dir_path):
                    os.rmdir(dir_path)
                    print(f"删除空目录: {dir_path}")
            except Exception as e:
                print(f"删除目录失败 {dir_path}: {e}")
    
    # 显示结果
    print("-" * 50)
    print(f"清理完成!")
    print(f"删除文件数: {deleted_count}")
    print(f"释放空间: {total_size / (1024 * 1024):.1f} MB")
    print(f"当前可用空间: {shutil.disk_usage(directory).free / (1024**3):.1f} GB")

# 使用示例
disk_cleaner('/tmp', days_old=7, extensions=['.log', '.tmp'])

常见问题解答

Q1:shutil.rmtree() 和 os.remove() 有什么区别?

A:

  • os.remove()只能删除文件
  • shutil.rmtree()可以删除整个目录(包括子目录和文件)

Q2:如何优雅地处理文件已存在的情况?

import shutil
import os

def safe_copy(src, dst):
    """安全复制,避免覆盖已有文件"""
    if os.path.exists(dst):
        base, ext = os.path.splitext(dst)
        counter = 1
        while os.path.exists(f"{base}_{counter}{ext}"):
            counter += 1
        dst = f"{base}_{counter}{ext}"
    
    shutil.copy2(src, dst)
    return dst

# 使用
new_path = safe_copy('file.txt', 'file.txt')

Q3:shutil能处理网络路径吗?

A:可以,但有限制。shutil可以处理UNC路径(如\server\share\file.txt),但性能可能不如本地文件系统。

Q4:如何显示复制进度?

import shutil
import sys

def copy_with_progress(src, dst):
    """带进度显示的复制"""
    total_size = os.path.getsize(src)
    copied = 0
    
    def callback(chunk_size):
        nonlocal copied
        copied += chunk_size
        percent = (copied / total_size) * 100
        sys.stdout.write(f"\r复制进度: {percent:.1f}%")
        sys.stdout.flush()
    
    shutil.copyfileobj(open(src, 'rb'), open(dst, 'wb'), callback=callback)
    print("\n复制完成!")

# 使用
copy_with_progress('large_file.iso', 'copy.iso')
posted @ 2026-04-27 18:17  MKYC  阅读(19)  评论(0)    收藏  举报