用 os.stat 获取文件修改时间需调用 fi.modtime() 与基准时间比较,须用 after/before 方法、截断纳秒精度,并检查 error;预检目标路径可写性;walkdir 时过滤符号链接和特殊文件;备份失败时用文本日志记录已备文件路径及截断后 modtime。

如何用 os.Stat 获取文件修改时间并判断是否需要备份
Go 中判断文件是否“新”(即自上次备份后有更新),核心是比对 os.FileInfo.ModTime() 与已知的基准时间。注意:不能直接用 time.Now().Sub() 粗略估算,必须读取真实元数据。
常见错误是忽略时区和纳秒精度导致误判——比如在 NFS 或某些容器挂载卷上,ModTime() 可能只精确到秒,若基准时间带纳秒,t1.After(t2) 可能返回意外结果。
- 始终用
time.Time.After()或time.Time.Before()比较,避免手动算秒数差 - 若需兼容低精度文件系统,可先用
.Truncate(time.Second)统一截断再比较 - 务必检查
os.Stat返回的 error,权限不足或路径不存在时会静默跳过,导致漏备
fi, err := os.Stat(srcPath)
if err != nil {
log.Printf("skip %s: %v", srcPath, err)
return false
}
// 假设 lastBackupTime 是从数据库或本地 timestamp 文件读出的 time.Time
return fi.ModTime().After(lastBackupTime)
备份前校验目标路径是否存在且可写
很多脚本在 os.Copy 时才报 no such file or directory,但此时源文件已处理完,逻辑中断难恢复。应在遍历阶段就预检目标目录。
尤其要注意:目标路径可能是相对路径,而当前工作目录(os.Getwd())可能不是你预期的;另外 Windows 下盘符大小写不敏感但 Go 的 os.Stat 对路径字符串严格匹配。
- 用
filepath.Dir(dstPath)提取父目录,再用os.Stat检查是否存在且是目录 - 用
os.WriteFile(dstPath, nil, 0644)尝试写空文件(随后os.Remove),确认有写权限 - 避免用
os.MkdirAll直接创建——如果父目录是只读挂载点,MkdirAll成功但后续写入仍失败
用 filepath.WalkDir 遍历时跳过符号链接和特殊文件
默认 filepath.WalkDir 会进入符号链接指向的目标,可能导致重复备份、循环遍历甚至跨分区误操作。生产环境必须显式控制。
另一个坑是设备文件(如 /dev/sda)或 FIFO,在 os.Open 时可能永久阻塞——哪怕只是想读 ModTime,也要先通过 fs.DirEntry.Type() 过滤。
- 在
WalkDirFunc中,先调entry.Type(),跳过os.ModeSymlink | os.ModeDevice | os.ModeNamedPipe - 对目录项,用
entry.Info()获取FileInfo,而非再次os.Stat—— 减少系统调用,也避免竞态(比如文件在两次 stat 间被删) - 若需排除隐藏文件(以
.开头),在函数开头加if strings.HasPrefix(entry.Name(), ".") { return nil }
备份失败时保留部分状态避免重复劳动
一次备份涉及成百上千个文件,中途 panic 或 Ctrl+C 会导致状态丢失。下次运行又得全量扫描+比对,浪费 I/O 和时间。
最轻量的做法是把“已成功备份的文件路径 + ModTime”追加写入一个临时状态文件(如 .backup_state.log),每完成一个文件就 flush 一行。重启后读该文件构建已备份集合,跳过它们。
- 状态文件格式建议为:
path|modtime_unix_nano(例如/a/b.txt|1718234567123456789),便于解析且无歧义 - 不要用 JSON 行或复杂结构——单行文本更容错,即使文件损坏也能按行切割恢复
- 写入前先
os.OpenFile(..., os.O_APPEND|os.O_CREATE|os.O_WRONLY),避免覆盖;写完后file.Sync()确保落盘
ModTime 的纳秒精度在跨平台同步时容易引发问题,比如 macOS HFS+ 和 ext4 的时间戳行为不同,实际使用中建议统一 truncate 到秒级存储和比对。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











