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')

浙公网安备 33010602011771号