跨分区重命名本质是拷贝+删除,因rename()系统调用仅同文件系统内原子执行,跨挂载点返回EXDEV错误;mv命令自动fallback,编程需显式捕获errno.EXDEV并处理。
rename() 系统调用,跨分区会失败Linux/macOS 下 rename() 仅在同文件系统内原子完成;一旦源路径和目标路径位于不同挂载点(比如 /home 和 /mnt/usb),系统直接返回 EXDEV 错误。这不是权限问题,而是内核限制——跨设备无法硬链接复用 inode,必须拷贝+删除。
mv 命令时自动 fallback 到拷贝+删除GNU coreutils 的 mv 已内置处理逻辑:先尝试 rename(),失败且错误为 EXDEV 时,自动改用 cp -f + rm -f 组合。但要注意:
mv 不加锁)mv 行为类似,但某些 BSD 变种不自动 fallback,需手动判断EXDEV
Python 示例(使用 os.rename()):
import os import shutildef safe_rename(src, dst): try: os.rename(src, dst) except OSError as e: if e.errno == errno.EXDEV: shutil.copy2(src, dst) # 保留时间戳和权限 os.unlink(src) else: raise
关键点:
OSError 并检查 e.errno == errno
.EXDEV,不能只靠异常类型shutil.copy2() 比 copy() 更安全,它复制元数据(mtime/ctime/mode)unlink() 失败(如权限不足),已拷贝的 dst 会残留,需额外清理逻辑跨分区重命名还涉及更隐蔽的问题:
mv 默认操作链接本身而非目标文件EXDEV 或表现异常,建议先用 stat -c '%d' path 比较设备号真正麻烦的不是“能不能动”,而是“动完状态是否可预期”——尤其当文件正被日志轮转、数据库写入或 rsync 同步时,跨分区 rename 实质是两次 I/O 操作,中间窗口期极难控制。