文件复制时保留原始时间戳
在复制文件时,默认情况下,新文件的创建时间通常会被设置为当前时间,而不会自动继承原文件的时间戳。但可以通过特定命令或工具,使新文件的创建时间、修改时间、访问时间与原文件完全一致。
以下是不同操作系统下的解决方案:
一、Windows 系统
使用 robocopy(推荐)
Windows 自带的 robocopy
(Robust File Copy)是最可靠的文件复制工具,其优势包括:
- 精确保留时间戳:使用
SetFileTime
和CopyFileEx
- 支持目录时间戳:
/DCOPY:T
保留目录的创建/修改/访问时间 - 高性能:支持多线程、断点续传、错误重试
- 拥有系统级的权限:可处理只读、隐藏文件
完整保留时间戳:
# 复制单个文件
robocopy "C:\源目录" "D:\目标目录" "文件名" /COPY:DAT /DCOPY:T /IS /IT /R:0 /W:0
# 复制整个目录
robocopy "C:\源目录" "D:\目标目录" /COPY:DAT /DCOPY:T /IS /IT /E /R:0 /W:0 /NFL /NDL /NP
参数说明:
/COPY:DAT
:复制数据(D)、属性(A)、时间戳(T)/DCOPY:T
:复制目录时间戳/E
:复制子目录(包括空目录)/R:0 /W:0
:不重试,失败后立即跳过/IS
:复制相同文件(不跳过)/IT
:复制相同文件的时间戳(即使数据没变)- 隐藏信息输出:
/NFL
:不列出文件名(No File List)/NDL
:不列出目录名(No Directory List)/NJH
:无作业标题(No Job Header)/NJS
:无统计摘要(No Job Summary)/NP
:无进度显示(No Progress)
二、Linux / macOS 系统
使用 cp 命令保留时间戳
cp -p "源文件" "目标文件"
-p
选项等价于--preserve=mode,ownership,timestamps
- 保留以下元数据:
- 权限(mode)
- 所有者与属组(ownership)
- 时间戳(timestamps):包括:
- mtime(Modification Time):文件内容最后修改时间
- atime(Access Time):文件最后访问时间
新生成的目标文件将具有:
- 创建时间(birth time / btime):设为当前时间(不可继承)
- inode 变更时间(ctime):设为当前时间(因创建新 inode)
- 新文件的 创建时间(birth time)和 inode 变更时间(ctime)将为当前时间,无法通过普通复制工具继承。
注意:Linux 中的
ctime
不是“创建时间”,而是 “元数据变更时间”(Change Time),每当文件权限、所有者、链接数或内容发生变化时自动更新,无法手动设置或复制。
Linux 文件系统三大时间戳
时间类型 | 含义 | 查看命令 |
---|---|---|
mtime | 内容修改时间 | stat file 或 ls -l (默认) |
atime | 访问时间(读取) | stat file 或 ls -lu |
ctime | inode 元数据变更时间 | stat file 或 ls -lc |
ls
命令:-l
默认显示 mtime,-lu
显示 atime,-lc
显示 ctime。
某些现代文件系统(如 ext4、XFS、Btrfs、APFS)支持 birth time(btime),可通过
stat
命令查看(字段名为Birth
),但标准工具(包括cp
)无法保留或复制该属性。
使用 scp 命令(跨平台)
scp -p /path/to/source/file user@remote:/destination/path/
-p
:尝试保留源文件的:- 修改时间(mtime)
- 访问时间(atime)
- 权限(permissions)
- 不保留:
- 所有者/属组(仅本地有效)
- 扩展属性、ACL
- 创建时间(birth time)
- ctime(自动更新为当前时间)
限制:
scp
功能较基础,适合简单场景。若需更完整的元数据同步,推荐使用rsync
。
使用 rsync 命令(跨平台)
rsync
是目前最强大、灵活且跨平台的文件同步工具,能精细控制元数据保留。
rsync
是一个强大且跨平台的文件同步工具,能够有效保留文件 权限、所有者、链接、mtime 和 atime(需显式指定) 等关键元数据。新文件的 创建时间(birth time)和 inode 变更时间(ctime)将为当前时间,无法通过普通复制工具继承。
本地到本地复制:
rsync -a --atimes /path/to/source/file /destination/path/
选项说明:
-a
:归档模式,等价于-rlptgoD
,包含:r
:递归复制目录l
:保留符号链接p
:保留权限t
:保留修改时间( mtime)g
、o
:保留属组和属主(需目标端有权限)D
:保留设备文件、FIFO 等特殊文件
--atimes
:显式请求保留源文件的访问时间( atime)
本地到远程复制:
rsync -a --atimes /path/to/source/file user@remote:/destination/path/
若需更全面地保留元数据(如扩展属性、ACL),可结合以下选项:
rsync -aAX --atimes /path/to/source/ user@remote:/destination/path/
-A
:保留 ACL(访问控制列表)-X
:保留扩展属性(extended attributes, xattrs)-a
:已包含-rlptgoD
--atimes
:保留atime
建议使用 rsync 3.2.0 及以上版本,对
--atimes
支持更稳定。旧版本可能不支持该选项或行为不一致。
最佳实践
cp
、scp
、rsync
均可在不同程度上保留 mtime 和 atime(需启用对应选项),但 无法继承创建时间(birth time)和 inode 变更时间(ctime)。这是由文件系统设计决定的限制。
推荐使用 rsync -aAX --atimes
实现最完整的元数据保留。
现代扩展:birth time(出生时间)
在现代文件系统中,birth time(也称为 file creation time 或 crtime)指的是文件或目录在其所在文件系统上首次被创建的精确时间。它由内核在文件对应的 inode 被分配时自动记录,是文件生命周期的“起点”。
支持 birth time
(创建时间)的文件系统主要包括:
- ext4 (Linux 4.11+):需新建卷且启用 inode version=2。
- XFS (Linux 5.x+):需新建卷。
- Btrfs、JFS、ZFS、APFS:原生支持
birth time
。
在这些文件系统上,birth time
作为 inode 的元数据被持久保存。
在 Linux 上,使用 stat file 命令可查看 Birth 字段信息:
stat filename
注意:
birth time
与mtime
(修改时间)、atime
(访问时间)、ctime
(状态变更时间)不同,它仅在文件创建时设置一次,之后永远不可修改。
当你使用常见的文件复制工具(如 cp
、rsync
、tar
等)复制文件时:
- 系统会为新文件分配一个新的 inode
- 新 inode 的
birth time
被设置为当前时间 - 原文件的
birth time
无法被写入新 inode
要真正保留文件的 birth time,必须在不重新创建 inode 的前提下进行数据复制。
唯一可行的方式是:文件系统级克隆(如 LVM 快照、btrfs send/receive、dd 镜像)。暂不赘述。
三、Python 脚本(跨平台)
在文件同步、备份或元数据敏感的场景中(如 Obsidian 笔记系统),精确保留文件时间戳(创建时间、修改时间、访问时间)至关重要。然而,Python 标准库中的 shutil
模块在跨平台时间戳处理上存在显著局限,尤其是在需要完整保留“创建时间”的场景下。
shutil.copy2() 的局限性
虽然 shutil.copy2()
声称“复制内容和元数据”,但在实际使用中存在以下问题:
-
无法保留创建时间(ctime):
shutil.copy2()
依赖os.utime()
来设置目标文件的时间戳。os.utime()
的限制:该函数仅支持设置两个时间戳:atime
(最后访问时间)mtime
(最后修改时间)
- 创建时间(Creation Time / Birth Time)无法通过
shutil
被复制或设置。新文件的创建时间将被设置为复制操作发生的时刻。
-
访问时间(atime)可能被干扰
- 读取干扰:在调用
shutil.copy2()
读取源文件时,操作系统通常会更新该文件的atime
(取决于文件系统挂载选项,如noatime
)。 - 写入干扰:目标文件在创建和写入过程中,其
atime
也可能被系统“修正”或默认更新。 - 即使
shutil
尝试复制原始atime
,其值也可能已不是文件最初的状态。
- 读取干扰:在调用
-
唯一可靠的(mtime)
- 在
shutil.copy2()
复制的三个时间戳中,只有mtime
(修改时间)是可靠且精确的。 mtime
是文件内容最后更改的时间,是同步和备份中最核心的判断依据。
- 在
因此,
shutil.copy2()
实际上只能保证mtime
的精确复制,atime
可能失真,而ctime
(创建时间)完全丢失。
跨平台时间戳处理差异
不同操作系统对文件时间戳的处理机制存在根本性差异。
时间戳类型 | Windows (NTFS) | Linux/macOS (ext4, APFS, etc.) |
---|---|---|
创建时间 | 支持读取和写入 (st_ctime ) |
通常只读 (birth time ),无法修改 |
修改时间 | 支持 (st_mtime ) |
支持 (st_mtime ) |
访问时间 | 支持 (st_atime ) |
支持 (st_atime ),但常被 noatime 禁用 |
元数据更改时间 | N/A | st_ctime 表示 inode 更改时间 |
关键差异:在 Windows 上可以主动修改文件的创建时间,而在 Linux/macOS 上,文件的“出生时间”一旦确定就无法更改。
总结
- 通用场景:使用
shutil.copy2()
足够,mtime
是同步的可靠依据。 - Windows 高保真场景:必须使用
pywin32
调用SetFileTime()
,才能实现与robocopy
相同的元数据完整性。 - Linux/macOS:无法修改文件的创建时间(birth time),这是系统级限制,一般工具都无法绕过。
Windows 高保真文件复制方案
为了在 Windows 平台上实现与 robocopy /COPY:DAT
相同的高保真效果(即完整复制数据、属性和所有时间戳),必须绕过 shutil
的限制,直接调用 Windows 原生 API。
原理机制
Windows 提供了 SetFileTime()
函数,允许程序精确设置文件的三个核心时间戳:
- Creation Time(创建时间)
- Last Access Time(最后访问时间)
- Last Write Time(最后写入时间,即
mtime
)
Python 实现:pywin32
库
Python 通过 pywin32
(pypiwin32
)包封装了对 Windows API 的调用,使得我们可以使用 win32file.SetFileTime()
实现高保真复制。
安装 pywin32
win32file
模块包含在 pywin32
包中。需要先安装它:
pip install pywin32
注意:安装后,有时需要运行一个额外的脚本来配置环境(尤其是在某些 IDE 或服务中)。如果遇到导入问题,可以尝试运行:
python Scripts/pywin32_postinstall.py -install
- 这个脚本通常位于您的 Python 安装目录的
Scripts
文件夹下。
安装 pywin32
的 stubs 文件
pip install pywin32-stubs
- 重启 IDE 或重新加载 Python 环境。
导入模块
import win32file
import win32con # 包含常量,如 GENERIC_READ, FILE_ATTRIBUTE_NORMAL 等
import pywintypes # 用于处理 Windows 时间类型
实现步骤
- 使用
shutil.copy2()
复制文件内容、基本属性和atime
/mtime
。 - 读取源文件的原始时间戳(
st_ctime
,st_atime
,st_mtime
)。 - 将 Unix 时间戳(秒)转换为 Windows
FILETIME
格式(100纳秒间隔,自 1601-01-01 UTC)。 - 使用
win32file.CreateFile()
获取目标文件句柄。 - 调用
win32file.SetFileTime()
精确设置三个时间戳。 - 关闭文件句柄。
代码实现
import shutil
import os
import win32file
import pywintypes
def set_file_times(path, ctime, atime, mtime):
"""
给文件或目录设置完整时间戳(Windows)时间戳
"""
handle = win32file.CreateFile(
path,
win32file.GENERIC_WRITE,
win32file.FILE_SHARE_READ | win32file.FILE_SHARE_WRITE,
None,
win32file.OPEN_EXISTING,
win32file.FILE_FLAG_BACKUP_SEMANTICS, # 支持目录
None
)
try:
# 设置完整时间戳
win32file.SetFileTime(handle, ctime, atime, mtime)
finally:
handle.close()
# print(f"文件复制完成,时间戳已继承:{dst}")
def copy_with_timestamps(src, dst):
"""
复制单个文件并完整保留时间戳(Windows 专用)
支持:创建时间、访问时间、修改时间
"""
# 1. 获取源文件时间戳
src_stat = os.stat(src)
ctime = pywintypes.Time(src_stat.st_ctime)
atime = pywintypes.Time(src_stat.st_atime)
mtime = pywintypes.Time(src_stat.st_mtime)
# 2. 使用 shutil.copy2 复制内容和基本元数据
shutil.copy2(src, dst)
# 3. 设置目标文件时间戳
set_file_times(dst, ctime, atime, mtime)
def copytree_with_timestamps(src, dst):
"""递归复制目录及其内容,并保留时间戳"""
dir_times = [] # 保存目录及其时间戳,最后统一设置
for root, dirs, files in os.walk(src):
rel_path = os.path.relpath(root, src)
target_subdir = os.path.join(dst, rel_path)
os.makedirs(target_subdir, exist_ok=True)
# 记录当前目录时间戳
root_stat = os.stat(root)
dir_times.append((target_subdir,
pywintypes.Time(root_stat.st_ctime),
pywintypes.Time(root_stat.st_atime),
pywintypes.Time(root_stat.st_mtime)))
# 复制文件及时间戳
for file in files:
src_file = os.path.join(root, file)
dst_file = os.path.join(target_subdir, file)
copy_with_timestamps(src_file, dst_file)
# 反向遍历设置目录时间戳(先子目录后父目录)
for path, ctime, atime, mtime in reversed(dir_times):
set_file_times(path, ctime, atime, mtime)
# 使用示例
src = r"D:\Obsidian\Middle\linkres\obsidian"
dst = r"D:\Obsidian\Middle\linkres\xcopy\obsidian"
# copywithtimestamps(src, dst)
copytree_with_timestamps(src, dst)
注意:此方法在 Windows 上通常有效,但某些系统策略(如防病毒软件、atime 延迟更新)可能干扰 atime
的精确性。
使用 Python 封装文件复制命令
- Windows 系统:使用
robocopy
命令进行文件和目录的复制,保留时间戳等属性。 - Unix/Linux/macOS 系统:使用
rsync
命令进行文件和目录的复制,支持保留时间戳、权限等属性。
import argparse
import subprocess
import os
import re
import shlex
import shutil
from typing import Optional
# 只在 Windows 平台导入 pywin32 模块
if os.name == 'nt':
try:
import win32file
import pywintypes
except ImportError:
print("请安装 pywin32 库以修复目录的时间戳")
def fix_directory_timestamps(src_dir: str, dst_dir: str):
"""
修复 Windows 下目标目录时间戳(创建、修改、访问)
"""
if not os.path.exists(dst_dir):
print(f"无法修复时间戳:目标目录不存在 {dst_dir}")
return
try:
src_stat = os.stat(src_dir)
ctime = pywintypes.Time(src_stat.st_ctime)
atime = pywintypes.Time(src_stat.st_atime)
mtime = pywintypes.Time(src_stat.st_mtime)
handle = win32file.CreateFile(
dst_dir,
win32file.GENERIC_WRITE,
win32file.FILE_SHARE_READ | win32file.FILE_SHARE_WRITE,
None,
win32file.OPEN_EXISTING,
win32file.FILE_FLAG_BACKUP_SEMANTICS, # 用于操作目录
None
)
try:
win32file.SetFileTime(handle, ctime, atime, mtime)
finally:
handle.close()
except Exception as e:
print(f"修复目录时间戳失败 {dst_dir}: {e}")
def is_target_directory(src: str, dst: str) -> bool:
"""
判断目标路径是否是目录(考虑隐藏文件特殊性)
"""
# 如果目标路径已存在,则判断是否是目录
if os.path.exists(dst):
if os.path.isdir(dst):
return True
else:
raise FileExistsError(f"目标路径已存在且不是目录: {dst}")
# 以路径分隔符结尾,则认为是目录
if dst.endswith('/') or dst.endswith('\\'):
return True
# 如果源是隐藏文件,且目标路径无扩展名但以 . 开头,则视为文件
if src and os.path.isfile(src):
src_name = os.path.basename(src)
if src_name.startswith('.'):
dst_name = os.path.basename(dst)
if dst_name.startswith('.') and os.path.splitext(dst)[1] == '':
return False
# 如果源是目录,则认为目标路径是目录
elif src and os.path.isdir(src):
return True
# 一般情况:如果目标路径无扩展名,则认为是目录
return os.path.splitext(dst)[1] == ''
def robocopy_copy(src: str, dst: str) -> bool:
"""
Windows 系统下使用 robocopy 复制文件或目录,保留时间戳(创建、修改、访问)
:param src: 源文件或目录路径
:param dst: 目标路径(文件或目录)
"""
if not os.path.exists(src):
raise FileNotFoundError(f"源路径不存在: {src}")
is_file = os.path.isfile(src)
src_name = os.path.basename(src)
dst_is_directory = is_target_directory(src, dst)
if dst_is_directory:
# 目标是目录,复制到该目录下,使用原文件名
parent_dst = dst.rstrip('/\\')
final_dst = os.path.join(parent_dst, src_name)
else:
# 目标是文件,直接使用目标文件名
parent_dst = os.path.dirname(dst) or '.' # 假如 data.txt 父目录为空,使用当前目录
final_dst = dst
# 确保父目录存在
os.makedirs(parent_dst, exist_ok=True)
if is_file:
parent_src = os.path.dirname(src)
file_list = [src_name]
else:
parent_src = src
file_list = []
# 优先使用 shell=False + 列表
# 构建 robocopy 命令
cmd = [
"robocopy",
parent_src, # 指定源目录
parent_dst, # 指定目标目录(robocopy 只支持目录)
*file_list, # 指定文件名列表
"/COPY:DAT", # 复制数据、属性、时间戳
"/DCOPY:T", # 复制目录时间戳(创建、修改、访问)
# "/E", # 包含子目录(含空目录)
"/R:0", "/W:0", # 不重试
"/NFL", "/NDL", # 不输出文件和目录
"/NJH", "/NJS", # 无作业头和尾
"/NC", "/NS", # 不输出文件大小、摘要
"/IS", # 复制相同文件(不跳过)
"/IT" # 复制相同文件的时间戳(即使数据没变)
]
if not is_file:
cmd.append("/E") # 包含子目录(含空目录)
try:
result = subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=300, # 5分钟超时
shell=False # 避免 shell 注入
# shell=True # 支持内建命令和变量替换
)
# robocopy 返回码:0~7 成功,8+ 失败
# 0: 无复制(文件已最新)
# 1: 成功复制文件
# 2: 有额外文件
# 3: 1+2
# 8+: 严重错误
success = result.returncode < 8
# 输出日志
if result.stdout.strip():
print("=== robocopy 输出 ===\n" + result.stdout)
if result.stderr.strip():
print("=== robocopy 错误 ===\n" + result.stderr)
if success:
# 文件场景:robocopy 实际复制到了 parent_dst/src_name,需重命名为 final_dst
if is_file:
temp_copied = os.path.join(parent_dst, src_name)
if os.path.exists(temp_copied) and temp_copied != final_dst:
os.replace(temp_copied, final_dst)
# 修复目录时间戳(可选)
if os.path.isdir(dst) and os.path.isdir(src):
fix_directory_timestamps(src, dst)
else:
print(f"复制失败(返回码: {result.returncode}")
return success
except subprocess.TimeoutExpired:
print("robocopy 执行超时")
return False
except Exception as e:
print(f"robocopy 执行失败: {e}")
return False
def remote_path_type(user_host: str, remote_path: str) -> Optional[str]:
"""
检查远程路径类型
:return: 'file', 'directory', 'link', 'not_exists', None(执行失败)
"""
quoted = shlex.quote(remote_path)
check_cmd = (
f"if [ -d {quoted} ]; then echo 'directory'; "
f"elif [ -f {quoted} ]; then echo 'file'; "
f"elif [ -L {quoted} ]; then echo 'link'; "
f"else echo 'not_exists'; fi"
)
cmd = ["ssh", user_host, check_cmd]
try:
result = subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=10,
encoding='utf-8',
errors='replace'
)
out = result.stdout.strip()
if out in ('file', 'directory', 'link', 'not_exists'):
return out
return None
except Exception as e:
print(f"SSH 检查失败: {e}")
return None
def ensure_remote_dir(user_host: str, remote_path: str) -> bool:
"""通过 SSH 确保远程目录存在"""
cmd = ["ssh", user_host, f"mkdir -p {shlex.quote(remote_path)}"]
try:
result = subprocess.run(
cmd,
capture_output=True,
text=True,
encoding='utf-8',
errors='replace'
)
return result.returncode == 0
except Exception as e:
print(f"创建远程目录失败: {e}")
return False
def rsync_copy(src: str, dst: str) -> bool:
"""
Unix 系统,使用 rsync 复制保留修改、访问时间戳
支持:本地到本地、本地到远程的复制
:param src: 源文件或目录路径
:param dst: 目标路径(文件或目录),支持 user@host:/path
"""
if not os.path.exists(src):
raise FileNotFoundError(f"源路径不存在: {src}")
src_path = src.rstrip('/') + '/' if os.path.isdir(src) else src
# 检查是否是远程路径
# is_remote = '@' in dst and ':' in dst
# 使用正则解析远程路径(支持 IPv6)
remote_match = r'^((?P<user>[^@]+)@)?(?P<host>\[[^\]]+\]|[^:]+):(?P<path>/.*)$'
match = re.match(remote_match, dst)
is_remote = bool(match)
if is_remote:
user = match.group('user') or ''
host = match.group('host')
user_host = f"{user}@{host}" if user else host
remote_path = match.group('path').rstrip('/')
remote_type = remote_path_type(user_host, remote_path)
if remote_type is None:
raise RuntimeError(f"无法确定远程路径类型:{dst}")
if os.path.isdir(src):
# 如果源是目录,则目标路径要确保是目录
if remote_type in ("directory", "link"):
final_dst = f"{user_host}:{remote_path}/"
elif remote_type == 'not_exists':
ensure_remote_dir(user_host, remote_path)
final_dst = f"{user_host}:{remote_path}/"
else:
raise RuntimeError(f"源是目录,目标不能是文件: {dst}")
else: # 源是文件
bname = os.path.basename(src)
if remote_type == 'not_exists':
if dst.endswith('/') or os.path.splitext(remote_path)[1] == '':
# 目标是目录
target_dir = remote_path.rstrip('/')
ensure_remote_dir(user_host, target_dir)
final_dst = f"{user_host}:{target_dir}/{bname}"
else:
parent_remote = os.path.dirname(remote_path)
if parent_remote.strip('/') != "": # 避免根目录
ensure_remote_dir(user_host, parent_remote)
final_dst = f"{user_host}:{remote_path}"
elif remote_type == 'directory':
final_dst = f"{user_host}:{remote_path}/{bname}"
else:
final_dst = f"{user_host}:{remote_path}"
else:
if os.path.isdir(src):
# 源是目录,目标路径要确保是目录
final_dst = dst.rstrip("/") + "/"
os.makedirs(final_dst, exist_ok=True)
else:
# 源是文件,目标路径判断
if dst.endswith('/') or os.path.splitext(dst)[1] == '':
dst = dst.rstrip('/')
final_dst = os.path.join(dst, os.path.basename(src))
else:
final_dst = dst
os.makedirs(os.path.dirname(final_dst), exist_ok=True)
# 构建 rsync 命令
cmd = ["rsync", "-a", "--atimes", src_path, final_dst]
try:
result = subprocess.run(
cmd,
capture_output=True,
text=True,
encoding='utf-8',
errors='replace',
timeout=300
)
if result.returncode == 0:
return True
outerr = result.stderr.lower()
if "permission denied" in outerr or "rsync error" in outerr:
cmd_sudo = ["sudo"] + cmd
try:
result2 = subprocess.run(
cmd_sudo,
capture_output=True,
text=True,
encoding='utf-8',
errors='replace',
timeout=300
)
return result2.returncode == 0
except Exception:
pass
print("rsync 失败:", result.stderr.strip())
return False
except subprocess.TimeoutExpired:
print("rsync 执行超时")
return False
except FileNotFoundError:
print("未找到 rsync,回退到 shutil.copy2")
return fallback_copy(src, dst)
except Exception as e:
print(f"rsync 复制失败: {e}")
return False
def fallback_copy(src: str, dst: str) -> bool:
"""回退复制方案(使用 shutil.copy2,保留基本时间戳)"""
try:
if os.path.isdir(src):
if os.path.exists(dst):
shutil.rmtree(dst)
shutil.copytree(src, dst, copy_function=shutil.copy2)
else:
os.makedirs(os.path.dirname(dst), exist_ok=True)
shutil.copy2(src, dst)
return True
except Exception as e:
print(f"回退复制失败: {e}")
return False
def copy_with_timestamps(src: str, dst: str) -> bool:
"""统一接口:复制并保留时间戳"""
if os.name == 'nt': # Windows
return robocopy_copy(src, dst)
else: # Unix/Linux/macOS
return rsync_copy(src, dst)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='复制文件或目录,保留时间戳')
parser.add_argument('src', type=str, help='源文件或目录路径')
parser.add_argument('dst', type=str, help='目标文件或目录路径')
args = parser.parse_args()
success = copy_with_timestamps(args.src, args.dst)
if success:
print("保留时间戳的复制成功!")
else:
print("保留时间戳的复制失败!")
exit(0 if success else 1)
# 使用示例
# Windows 系统:
# 源是目录:src = r'C:\source\dir'
# 源是文件:src = r'C:\source\file.txt'
# 目标: dst = r'D:\backup\dir' 或 r'D:\backup\file.txt'
# Linux / macOS:
# 本地复制:
# src = '/local/source/dir'
# dst = '/local/backup/dir'
# 复制到远程:
# src = '/local/source/dir'
# dst = 'user@remote:/remote/backup/dir'
# 命令行调用:
# sudo python3 copywithtimestamps.py /local/source/dir /local/backup/dir
# sudo python3 copywithtimestamps.py /local/source/file.txt user@remote:/remote/backup/