shutil模块 — 高级文件操作

Python标准库精讲专题 · 文件与目录处理篇 · 掌握高级文件操作

专题:Python标准库精讲系统学习

关键词:Python, 标准库, shutil, 文件复制, 目录复制, rmtree, make_archive, disk_usage, which, 文件管理

一、shutil模块概述

shutil是Python标准库中"shell utilities"的缩写,位于官方文档的"文件与目录处理"分类下。它建立在os模块和os.path模块之上,提供了更高层次的文件操作接口。如果说os模块提供的是操作系统底层的"砖块",那么shutil就是把这些砖块砌成实用工具的"泥瓦匠"。

在日常开发中,我们经常需要执行文件复制、目录备份、归档压缩等操作。如果直接用os模块来实现这些功能,需要手动处理大量细节——打开文件、读取字节流、处理权限、递归遍历目录等。shutil模块将这些高频操作封装成一行函数调用,极大减少了代码量。

shutil模块的核心功能可以归纳为六大类:文件复制与移动、目录复制与删除、文件查找、归档打包、磁盘空间查询以及临时文件处理。其中文件复制是最常用的功能,Python提供了从简单到复杂的多个复制函数,满足不同场景的需求。

学习提示:shutil模块是Python文件操作中的"瑞士军刀"。掌握shutil后,日常的文件管理脚本编写效率会大幅提升。建议读者在学习过程中打开Python交互环境,边学边试,加深理解。

二、文件复制

文件复制是shutil模块最常用的功能。针对不同的使用场景,shutil提供了四个层次的文件复制函数,分别控制着复制内容的深度——从纯文件内容,到权限位,再到所有元数据(修改时间、创建时间等)。

2.1 copyfileobj — 文件对象级别的复制

copyfileobj是最底层的文件复制函数,它直接操作已经打开的文件对象。调用者需要自己以合适的模式打开源文件和目标文件,然后将文件对象传递给该函数。它本质上是在循环调用文件对象的read和write方法,将源文件的数据块逐步写入目标文件。

import shutil # 以二进制模式打开源文件和目标文件 with open('source.dat', 'rb') as src: with open('dest.dat', 'wb') as dst: shutil.copyfileobj(src, dst) # 可以指定缓冲区大小(默认16KB) with open('source.dat', 'rb') as src: with open('dest.dat', 'wb') as dst: shutil.copyfileobj(src, dst, length=1024*1024) # 1MB缓冲区

copyfileobj接收一个可选的length参数用于控制每次读取的字节数。较大的length值可以减少系统调用次数,适合大文件复制;较小的值则占用内存更少,适合在内存受限的环境中使用。

2.2 copyfile — 只复制文件内容

copyfile是更高一层的封装,调用者只需要传入源文件路径和目标文件路径即可。它在内部完成了打开文件的操作,本质上是调用了copyfileobj来完成实际的数据传输。需要注意,copyfile只复制文件内容,不复制权限信息和元数据,并且要求目标路径必须是一个完整的文件路径(不能是目录)。

import shutil # 复制文件内容,覆盖目标文件 shutil.copyfile('source.txt', 'dest.txt') # 如果目标文件已存在,会被直接覆盖 # 如果目标路径是目录,抛出 IsADirectoryError # 如果源文件不存在,抛出 FileNotFoundError

copyfile还有一个重要的限制——它不允许复制到目录路径。这意味着你必须明确指定目标文件的完整名称,而不能像命令行copy命令那样只指定目标目录。

2.3 copy — 复制文件内容 + 权限

copy函数在copyfile的基础上增加了权限复制的能力。它会先复制文件内容(调用copyfile),然后复制文件的权限位到目标文件。此外,copy函数允许目标路径是一个目录——如果目标路径是目录,它会自动将源文件名附加到目标路径后面。

import shutil # 复制到具体文件路径 shutil.copy('source.txt', 'dest.txt') # 复制到目录(自动保留源文件名) shutil.copy('source.txt', '/path/to/backup/') # 结果创建 /path/to/backup/source.txt

copy函数的返回值是目标文件的实际路径。这是一个实用的设计——当你把文件复制到一个目录时,返回值会告诉你最终文件的完整路径。

2.4 copy2 — 复制文件内容 + 保留所有元数据

copy2是shutil文件复制函数中"最完整"的一个。它在copy的基础上额外保留了源文件的所有元数据,包括修改时间、访问时间以及部分平台相关的属性(如Windows上的创建时间)。它通过调用os.utime和os.chmod等函数来实现元数据的完整保留。

import shutil # 复制文件内容并保留所有元数据 shutil.copy2('source.txt', 'backup/source.txt') # 适用于备份场景,保留原始时间戳 # 在文件备份和同步工具中使用广泛

在备份场景中,保留原始时间戳非常重要——它让备份文件看起来和原始文件完全一致,便于后续比较和审计。

核心对比:四个复制函数的适用场景:copyfileobj适合需要精细控制复制过程的底层需求(如大文件分段复制、网络流复制);copyfile适合仅需要文件内容且目标路径明确的场景;copy适合日常复制操作,保留权限信息对脚本执行环境很有用;copy2适合备份归档场景,要求完整保留文件元信息。

三、目录复制

3.1 copytree — 递归复制整个目录树

copytree函数是shutil中最强大的复制功能之一。它不仅能复制单个文件,还能递归地复制整个目录结构——包括所有子目录、子目录中的文件、符号链接等。默认情况下,copytree会复制目录中的所有内容,并尽量保留文件和目录的元数据。

import shutil # 复制整个目录树 shutil.copytree('project/', 'backup/project/') # 注意:目标目录不能已存在 # 这与Unix的 cp -r 命令行为不同

copytree有一个容易忽略的重要行为:目标目录必须不存在。这与Unix系统上cp -r命令的行为不同,后者会在目标目录已存在时将源目录复制到目标目录内部。copytree的严格设计是为了避免误覆盖已有数据。

3.2 symlinks参数 — 符号链接处理

默认情况下,copytree会跟随符号链接(symlinks=False),即遇到符号链接时复制它指向的实际文件内容。如果设置symlinks=True,则复制符号链接本身而非其指向的内容。

import shutil # 复制符号链接本身(而不是指向的文件) shutil.copytree('project/', 'backup/project/', symlinks=True) # 默认行为:跟随符号链接,复制实际文件 shutil.copytree('project/', 'backup/project/', symlinks=False)

这个参数在备份场景中尤为重要:如果你希望备份保留原始目录中的链接关系,就设置为True;如果你需要一份"扁平化"的完整文件副本,就使用默认值False。

3.3 ignore参数 — 过滤排除文件

copytree的ignore参数允许传入一个函数,该函数接收目录路径和文件列表作为参数,返回需要忽略的文件名列表。shutil提供了便捷的ignore_patterns辅助函数,通过通配符模式快速指定需要排除的文件。

import shutil # 排除所有 .pyc 文件和 __pycache__ 目录 shutil.copytree( 'project/', 'backup/project/', ignore=shutil.ignore_patterns('*.pyc', '__pycache__') ) # 自定义忽略函数 def my_ignore(dir, files): return [f for f in files if f.endswith('.log') or f.startswith('.')] shutil.copytree('project/', 'backup/project/', ignore=my_ignore)

ignore参数是copytree中最实用的功能之一。它让你在复制目录时像.gitignore一样灵活地排除不需要的文件,非常适合于项目构建和部署场景中的选择性复制。

四、目录删除与移动

4.1 rmtree — 递归删除目录

rmtree是shutil中最"危险"的函数——它会递归地删除整个目录树及其所有内容。与os.remove只能删除文件、os.rmdir只能删除空目录不同,rmtree可以一次性删除非空目录,无论其层级多深。

import shutil # 递归删除整个目录树 shutil.rmtree('/path/to/temp/dir/') # 处理可能出现的错误 try: shutil.rmtree('temp/') except OSError as e: print(f'删除失败: {e}')

安全提示:rmtree操作不可逆。在生产环境中使用rmtree之前,建议先打印路径确认,或者先将内容移动到回收站(可通过send2trash库实现)。

rmtree还有一个可选参数ignore_errors,设置为True时会在遇到无法删除的文件时忽略错误继续删除其他内容,而不是抛出异常。

4.2 move — 文件或目录移动

move函数用于移动文件或目录到目标位置,类似于Unix的mv命令。它的实现策略是:先尝试在同一文件系统内使用os.rename进行快速重命名;如果失败(例如跨文件系统移动),则回退为复制+删除的保守方案。

import shutil # 文件移动 shutil.move('source.txt', '/path/to/dest/') # 文件重命名(在同一目录下移动) shutil.move('old_name.txt', 'new_name.txt') # 目录移动(跨文件系统时自动复制后删除) shutil.move('project/', '/mnt/backup/project/')

move函数的智能之处在于它自动处理跨文件系统移动。当你把文件从C盘移动到D盘时,操作系统底层不支持直接重命名,move会自动改为复制+删除的策略,对调用方完全透明。

4.3 chown 与 copymode — 权限更改

shutil提供了几个辅助函数用于文件权限管理。chown函数用于更改文件的所有者和用户组(仅Unix/Linux系统支持);copymode用于将一个文件的权限位复制到另一个文件。

import shutil import os # 更改文件所有者(Unix only) shutil.chown('file.txt', user='nobody', group='nogroup') # 复制权限位 shutil.copymode('source.txt', 'dest.txt') # 复制文件元数据(权限+时间戳) shutil.copystat('source.txt', 'dest.txt')

copystat函数是copy2内部使用的工具函数,它完整地复制文件的权限位、修改时间、访问时间等元数据,但不涉及文件内容的复制。

五、归档操作

shutil模块的归档功能是在Python 3.x中逐步完善的。它提供了统一的接口来创建和解压常见格式的归档文件,底层支持zip、tar、gztar、bztar和xztar等多种格式。这个功能最大的优势在于不需要额外安装第三方库就能完成基本的归档操作。

5.1 make_archive — 创建归档文件

make_archive是创建归档的核心函数。它接收一个根目录(root_dir)和一个要归档的基目录(base_dir),将目录内容打包成指定格式的归档文件。如果base_dir没有指定,则归档root_dir本身的所有内容。

import shutil # 将 project/ 目录打包为 zip 文件 shutil.make_archive( 'project_backup', # 输出文件名(不含扩展名) 'zip', # 归档格式 'project/' # 要归档的目录 ) # 生成 project_backup.zip # 指定 base_dir 仅归档子目录 shutil.make_archive( 'html_backup', 'gztar', # .tar.gz 格式 root_dir='website/', base_dir='html/' # 只归档 website/html/ 下的内容 ) # 生成 html_backup.tar.gz

5.2 获取支持的归档格式

shutil提供了两个函数来查询系统当前支持的归档格式。不同的操作系统和Python编译选项会影响支持的格式范围。在编写跨平台部署脚本时,建议先查询可用格式再做决定。

import shutil # 获取支持的归档格式列表 formats = shutil.get_archive_formats() print(formats) # 输出示例:[('bztar', "bzip2'ed tar-file"), # ('gztar', "gzip'ed tar-file"), # ('tar', 'uncompressed tar file'), # ('xztar', "xz'ed tar-file"), # ('zip', 'ZIP file')] # 获取支持的解压格式 unpack_formats = shutil.get_unpack_formats() print(unpack_formats)

5.3 unpack_archive — 解压归档文件

unpack_archive是make_archive的逆操作,支持自动识别归档格式并进行解压。你只需要提供归档文件路径和解压目标目录,shutil会自动判断格式并选择合适的解压方式。

import shutil # 解压归档文件到指定目录 shutil.unpack_archive('project_backup.zip', 'restored/') # 不指定 extract_dir 则解压到当前目录 shutil.unpack_archive('project_backup.zip')

unpack_archive的自动格式识别功能使其在编写通用文件处理工具时非常方便——无需针对每种归档格式编写不同的解压逻辑。

实用技巧:结合make_archive和rmtree,可以轻松实现"备份后清理"的自动化流程。这在CI/CD流水线、日志轮转、临时目录清理等场景中非常实用。

六、系统查询

shutil提供的系统查询功能可以帮助脚本智能地做出决策——例如在磁盘空间不足时跳过备份,或者查找系统中某个可执行程序的确切路径。

6.1 disk_usage — 查询磁盘使用情况

disk_usage函数接收一个路径作为参数,返回该路径所在磁盘分区的总空间、已用空间和可用空间(以字节为单位)。返回值是一个命名元组,可以通过属性名或索引访问。

import shutil # 查询当前工作目录所在磁盘的使用情况 usage = shutil.disk_usage('.') print(f'总空间: {usage.total / (1024**3):.2f} GB') print(f'已用空间: {usage.used / (1024**3):.2f} GB') print(f'可用空间: {usage.free / (1024**3):.2f} GB') # 在备份前检查磁盘空间 MIN_SPACE = 500 * 1024 * 1024 # 500MB if shutil.disk_usage('/backup/').free < MIN_SPACE: print('磁盘空间不足,跳过备份') else: print('开始备份...')

disk_usage最常用的场景是在执行大文件操作前做空间预检。例如,在备份脚本中先检查目标磁盘是否有足够空间,避免操作中途失败导致数据不完整。

6.2 which — 查找可执行程序路径

which函数模拟了操作系统的PATH查找逻辑,在环境变量PATH指定的目录列表中搜索匹配的可执行文件。它返回第一个匹配的完整路径,如果找不到则返回None。这个函数在跨平台脚本中特别有用——无论是Windows的.exe还是Linux的可执行文件,which都能正确处理。

import shutil # 查找 python 可执行文件路径 python_path = shutil.which('python') print(f'Python 路径: {python_path}') # Linux 输出示例: /usr/bin/python # Windows 输出示例: C:\Python39\python.exe # 查找 git if shutil.which('git'): print('系统中已安装 Git') else: print('Git 未安装,请先安装') # 检查多个工具是否就绪 tools = ['ffmpeg', 'convert', 'wkhtmltopdf'] missing = [t for t in tools if not shutil.which(t)] if missing: print(f'缺少以下工具: {missing}')

which函数最常见的应用场景是环境依赖检查——在脚本执行前验证所有依赖的外部工具是否可用。例如,一个视频处理脚本可以在启动时检查ffmpeg是否已安装,避免执行到一半才发现环境不完整。

七、实战案例与总结

7.1 案例一:目录自动备份脚本

以下是一个综合运用shutil多个功能的自动备份脚本。该脚本将指定目录打包为带时间戳的备份文件,并在备份完成后验证归档完整性。它综合运用了make_archive、disk_usage和which等函数。

import shutil import os from datetime import datetime def backup_directory(source_dir, backup_root): """自动备份目录到指定位置""" # 1. 检查源目录是否存在 if not os.path.isdir(source_dir): print(f'错误: 源目录 {source_dir} 不存在') return False # 2. 检查目标磁盘空间 min_space = 100 * 1024 * 1024 # 100MB try: usage = shutil.disk_usage(backup_root) if usage.free < min_space: print(f'磁盘空间不足 (可用: {usage.free / 1024**2:.1f}MB)') return False except FileNotFoundError: print(f'备份目录 {backup_root} 不存在,尝试创建...') os.makedirs(backup_root, exist_ok=True) # 3. 生成带时间戳的备份文件名 timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') dir_name = os.path.basename(os.path.normpath(source_dir)) archive_name = os.path.join(backup_root, f'{dir_name}_{timestamp}') # 4. 创建归档 print(f'正在备份 {source_dir} ...') archive_path = shutil.make_archive( archive_name, 'zip', source_dir ) print(f'备份完成: {archive_path}') return True # 使用示例 backup_directory('/home/user/projects/myapp/', '/backup/')

7.2 案例二:文件整理与分类工具

以下脚本演示了如何使用shutil.move和os.walk来按文件扩展名自动分类整理目录中的文件。这是日常中最常用的文件管理脚本模式。

import shutil import os from pathlib import Path def organize_directory(target_dir): """按文件类型整理目录""" # 定义文件分类规则 categories = { 'images': ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.svg'], 'documents': ['.pdf', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx'], 'archives': ['.zip', '.tar', '.gz', '.rar', '.7z'], 'code': ['.py', '.js', '.html', '.css', '.java', '.cpp', '.h'], 'videos': ['.mp4', '.avi', '.mkv', '.mov', '.flv'], 'music': ['.mp3', '.wav', '.flac', '.aac'], } for item in os.listdir(target_dir): item_path = os.path.join(target_dir, item) # 跳过目录 if os.path.isdir(item_path): continue # 根据扩展名分类 ext = os.path.splitext(item)[1].lower() moved = False for category, extensions in categories.items(): if ext in extensions: dest_dir = os.path.join(target_dir, category) os.makedirs(dest_dir, exist_ok=True) shutil.move(item_path, os.path.join(dest_dir, item)) print(f'移动: {item} -> {category}/') moved = True break if not moved: # 未分类的文件放入 others 目录 other_dir = os.path.join(target_dir, 'others') os.makedirs(other_dir, exist_ok=True) shutil.move(item_path, os.path.join(other_dir, item)) print(f'移动: {item} -> others/') # 使用示例 organize_directory('/home/user/Downloads/')

7.3 总结与核心要点

shutil模块作为Python标准库中文件操作的高级封装,其设计哲学可以概括为"让常见的文件操作变得简单"。回顾全文,我们梳理了六大核心功能:文件复制提供了从底层(copyfileobj)到完整(copy2)的四个层次,满足不同的精细化需求;目录复制通过copytree和灵活的参数(symlinks、ignore)实现了对目录树的精准控制;删除与移动功能(rmtree、move)弥补了os模块中递归操作的不足;归档功能(make_archive、unpack_archive)将压缩和解压操作统一为简洁的接口;系统查询功能(disk_usage、which)让脚本能够感知运行环境,做出智能决策。

在实际开发中,shutil最适合的场景包括:编写备份和同步脚本、构建部署流水线、开发文件管理工具、以及任何需要批量化处理文件和目录的自动化任务。掌握shutil,能让你的文件操作代码从繁琐的底层细节中解放出来,专注于真正的业务逻辑。

进阶提示:如果你需要更高级的文件监控(如监听文件变化)或更复杂的文件同步(如增量同步),可以进一步学习watchdog和rsync等外部工具。但80%的日常文件管理需求,shutil已经足够胜任。