shutil.copytree 默认不保留符号链接,会尝试展开导致报错;设 symlinks=True 可原样复制链接,dirs_exist_ok=True 可避免目标存在错误,但链接路径有效性及重写需手动处理。

shutil.copytree 默认不处理符号链接,会直接报错
当你用 shutil.copytree 拷贝含符号链接的目录时,如果目标路径已存在(比如链接指向的文件或目录不在源树内),默认行为会抛出 OSError: [Errno 17] File exists 或 FileNotFoundError。根本原因是它尝试“展开”符号链接并递归复制目标内容,而不是原样复制链接本身。
用 symlinks=True 参数保留符号链接原始结构
最直接的解法是显式传入 symlinks=True,让 copytree 把符号链接当作普通文件复制(即只拷贝链接路径字符串,不解析目标):
import shutil
shutil.copytree("/path/to/src", "/path/to/dst", symlinks=True)
- 这个参数在 Python 3.2+ 全版本支持,无需额外判断
- 对硬链接无效——
copytree从不复制硬链接,只会复制指向同一 inode 的新文件(即“硬链接语义丢失”) - 若源中某符号链接指向绝对路径(如
/etc/hosts),目标里也会生成相同绝对路径的链接,运行时可能失效,需自行检查或重写
需要解析并重定向符号链接?得自己实现 walk + copy
如果业务要求把符号链接的目标也一并拷贝进目标目录,并更新链接指向相对路径(比如把 ../config.yaml 改成 ../dst/config.yaml),copytree 就不够用了。此时必须手动遍历:
- 用
os.walk遍历源目录,对每个os.path.islink(path)为True的项单独处理 - 用
os.readlink(path)获取原始目标路径,再用os.path.relpath()转为目标树内的相对路径 - 用
os.symlink(target, dst_path)创建新链接,注意确保父目录已存在(os.makedirs(..., exist_ok=True)) - 普通文件仍可用
shutil.copy2()保持时间戳和权限
这种逻辑绕过了 copytree 的封装,但换来完全可控的符号链接行为。
让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限
Python 3.8+ 可用 dirs_exist_ok=True 避免重复创建错误
如果你多次运行拷贝脚本、且目标目录可能残留旧内容,copytree 默认会因目标存在而失败。加 dirs_exist_ok=True 可跳过“目标已存在”的校验:
shutil.copytree(src, dst, symlinks=True, dirs_exist_ok=True)
- 仅影响目录创建阶段,不影响文件覆盖行为(已有同名文件仍会报错)
- 与
symlinks=True组合使用很常见,但要注意:它不会自动清理旧链接或旧文件,只是允许进入复制流程 - 若需真正“同步”而非“覆盖”,还是得用
rsync或distutils.dir_util.copy_tree(已弃用,不推荐)
符号链接的处理从来不是非黑即白的选择——保留原始路径还是重写为相对路径,取决于你的部署场景是否允许外部依赖。别指望一个参数解决所有问题,先想清楚“这个链接在目标环境里该指向谁”。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










