Python shutil.copy2:内容复制成功,权限、时间和所有权是否也跟过去了
文件字节一样,恢复后的行为仍可能不同
把脚本复制到另一处以后,内容可以正常打开,执行权限或修改时间却发生变化。备份清单如果只记录“复制成功”,就容易把文件内容与文件属性混在一起。Python 提供几种高层复制函数,它们的区别不仅在名字,验收时也应该分别检查字节和必要属性。
copyfile 主要复制内容,copy 还复制权限模式;copy2 在此基础上尝试保留更多元数据,通常包括访问与修改时间。这里的“尝试”很关键:跨平台、跨文件系统时能够保留的属性不同,不能把 copy2 当作完整保真备份工具的同义词。
固定一个旧时间,让差异可以直接观察
下面示例针对 Linux,使用 Python 标准库,在三点十二验证。它只在自己创建的临时目录内设置样本文件模式与时间,不改变系统配置或现有业务文件。修改时间选择固定旧值,所以无需等待,也不会把运行快慢当作复制是否保留时间的证据。
代码分别创建普通副本和保留元数据的副本,在进一步读取内容之前采集属性。这样能减少访问时间受读取动作影响的干扰。随后检查字节一致,再修改一份副本,确认普通复制得到独立内容,而不是两个路径共享同一个文件内容对象。
AI概念配图,非真实界面
import os
from pathlib import Path
import shutil
import stat
from tempfile import TemporaryDirectory
with TemporaryDirectory() as folder:
root = Path(folder)
source = root / 'source.txt'
plain = root / 'plain.txt'
preserved = root / 'preserved.txt'
source.write_bytes(b'original\n')
source.chmod(0o640)
old_ns = 946684800 * 1_000_000_000
os.utime(source, ns=(old_ns, old_ns))
shutil.copy(source, plain)
shutil.copy2(source, preserved)
src_stat = source.stat()
plain_stat = plain.stat()
kept_stat = preserved.stat()
mode = stat.S_IMODE(src_stat.st_mode)
assert mode == 0o640
assert stat.S_IMODE(plain_stat.st_mode) == mode
assert stat.S_IMODE(kept_stat.st_mode) == mode
assert plain_stat.st_mtime_ns != src_stat.st_mtime_ns
assert kept_stat.st_mtime_ns == src_stat.st_mtime_ns
assert source.read_bytes() == plain.read_bytes() == preserved.read_bytes()
print('both contents match: True')
print('copy mode:', oct(stat.S_IMODE(plain_stat.st_mode)))
print('copy keeps old mtime:', plain_stat.st_mtime_ns == src_stat.st_mtime_ns)
print('copy2 keeps old mtime:', kept_stat.st_mtime_ns == src_stat.st_mtime_ns)
preserved.write_bytes(b'changed\n')
assert source.read_bytes() == b'original\n'
print('source unchanged after editing copy: True')
link = root / 'source-link'
link.symlink_to(source.name)
copied_link = root / 'copied-link'
shutil.copy2(link, copied_link, follow_symlinks=False)
assert copied_link.is_symlink()
assert os.readlink(copied_link) == source.name
print('link copied as link: True')
print('all copy metadata checks passed')只验证自己真正依赖的属性
两个副本内容相同,普通 copy 的模式也是零六四零,但它没有保留固定旧修改时间;copy2 保留了该时间。这个结果说明内容、模式和时间应逐项检查,不能拿一个属性相等推断其他属性也相等。代码使用从源文件重新读取的时间作为比较值,避免假定文件系统支持任意精度。
访问时间尤其容易受到后续读取影响。有些文件系统还会采用不同的访问时间更新策略,因此本文没有承诺复制后的访问时间永远与最初设置值一致。创建时间与状态变更时间也不能简单视为可通过常规复制原样保留的修改时间替代品。
官方文档明确提醒,高层复制函数无法复制所有元数据。对 POSIX 平台,所有者、所属组和访问控制列表不能因此获得完整保留保证;其他平台还有各自的扩展属性与数据流限制。即使同一用户创建两个文件后所有权恰好相同,也不能证明复制函数主动复制了所有权。
链接策略和内容独立性分别确认
默认跟随符号链接时,复制通常针对链接指向的内容。示例最后显式关闭跟随,复制得到的仍是符号链接,保存的相对目标文字与原链接一致。若把这个链接放到不同目录,相对目标的解释位置也会改变,所以“链接文字一样”不保证最终指向同一文件。
复制后修改目标没有改变源文件,证明这个样本不是硬链接。对于大文件,底层实现可能采用平台优化,但应用仍应依据复制接口的内容语义工作,不要凭底层技术名称推断两个路径是否可以互相修改。需要特殊存储语义时,应另行核对具体工具的承诺。
目标路径已存在时,复制可能覆盖原有内容;源文件与目标是同一文件等情况则会失败。实际批处理应在调用前规划目标命名、冲突策略和失败记录,并确保只操作自己确定的路径。复制成功以后再核对必需属性,才适合把该项标成完成。
如果任务要求恢复整套权限、所有权或平台专有属性,应选择能表达这些要求的备份方案,并做实际恢复演练。copy2 很适合日常文件处理中的内容与常用属性保留,但可靠的验收标准应该来自恢复用途,而不是函数名称里的数字。


