唯一可靠方式是调用os.Stat并用os.IsNotExist(err)检查错误类型,因系统调用返回的错误被包装(如&os.PathError),直接err==os.ErrNotExist恒为false;os.Stat跟随符号链接,os.Lstat不跟随;避免TOCTOU竞态,勿先Stat再ReadFile。

Go 没有 os.Exists,也不能用 err == os.ErrNotExist 判断文件是否存在——唯一可靠方式是调用 os.Stat 并用 os.IsNotExist(err) 检查错误类型。
为什么不能直接比较 err == os.ErrNotExist
因为系统调用(如 stat)返回的错误通常是包装过的,比如 &os.PathError{Err: syscall.ENOENT}(Linux/macOS)或类似 Windows 错误对象,os.ErrNotExist 仅用于构造错误,不是运行时实际返回值。直接比较恒为 false,导致逻辑永远走错分支。
常见错误现象:
- 代码看似能跑通,但在某些系统或权限受限路径下误判“存在”
- CI/CD 环境中因容器挂载问题返回非
ENOENT错误,却被当成“不存在”处理
os.Stat 和 os.Lstat 怎么选
os.Stat 默认跟随符号链接,检查的是链接指向的目标是否存在;os.Lstat 不跟随,只检查链接文件本身。
立即学习“go语言免费学习笔记(深入)”;
使用场景:
- 想确认“这个路径能不能读取内容” → 用
os.Stat - 想确认“这个软链接文件自己在不在” → 用
os.Lstat - 检查配置文件、日志路径等常规路径 → 优先
os.Stat
示例:
_, err := os.Stat("/path/to/symlink")
// 若 /path/to/symlink → /real/file,且 /real/file 不存在,则 err != nil 且 os.IsNotExist(err) == true
<p>_, err := os.Lstat("/path/to/symlink")
// 即使 /real/file 不存在,只要 /path/to/symlink 这个 symlink 文件存在,err == nil
别为了“判断存在”而多一次系统调用
如果后续紧接着要读文件(比如用 os.ReadFile),**不要先 os.Stat 再 os.ReadFile** ——这会引发竞态(TOCTOU):两次调用之间文件可能被删/创建,且浪费一次 syscall。
更稳妥的做法:
- 直接
os.ReadFile(path),然后用os.IsNotExist(err)处理“不存在”分支 - 若需要文件元信息(大小、修改时间等),再复用
os.Stat结果 - 避免用
os.Open做存在性检查:它会打开 fd,增加资源开销和泄漏风险
Windows 下要注意路径分隔符和权限误报
Windows 对路径大小写不敏感,但 os.Stat 在某些 NTFS 配置下可能对隐藏/系统属性文件返回权限错误而非 os.ErrNotExist,尤其当程序以非管理员身份运行时。
建议:
- 不要假设
err != nil就等于“不存在”,必须显式用os.IsNotExist(err) - 遇到非
os.IsNotExist的错误(如os.IsPermission(err)),按访问失败处理,而不是跳过或静默忽略 - 测试时覆盖
C:\temp\、网络驱动器、OneDrive 同步目录等典型 Windows 路径
最易被忽略的一点:你写的 exists 工具函数,如果返回 (false, err) 且 err 是权限错误,调用方很可能只看 false 就认为“文件不存在”,从而掩盖了真正的权限问题。


















