os.Stat可判断路径存在但不区分文件与目录,需用os.IsNotExist(err)精准识别不存在错误,避免误判权限等问题;严格区分类型需调用fi.IsDir();高频场景应考虑缓存;符号链接检测应使用os.Lstat;避免TOCTOU竞态,推荐直接执行操作并处理错误。

用 os.Stat 判断路径存在,但要注意它不区分文件和目录
Go 没有直接的 exists 函数,最常用的是 os.Stat。它返回 os.FileInfo 和错误;如果路径不存在,错误是 os.IsNotExist(err) 为 true 的具体错误(比如 "no such file or directory")。但注意:os.Stat 成功只说明路径存在,不管它是文件还是目录——哪怕你传入一个目录路径,它也成功返回,不会报错。
常见误用:只检查 err != nil 就认为“不存在”,忽略了权限不足(os.IsPermission(err))或设备不可用等非不存在类错误,导致误判。
- 正确做法:先判断
os.IsNotExist(err),再处理其他错误(如权限问题应单独告警,而非当成“不存在”) - 若需严格区分“是文件”或“是目录”,必须在
os.Stat成功后调用fi.IsDir() -
os.Stat会触发一次系统调用,对性能敏感场景(如高频扫描)要考虑缓存或批量 stat
用 os.Lstat 处理符号链接时的特殊需求
如果路径可能是符号链接,且你想检测“链接本身是否存在”(而不是它指向的目标),就得用 os.Lstat。例如:/tmp/link 是个坏链接(指向已删除路径),os.Stat 会失败(因为目标不存在),而 os.Lstat 能成功返回链接自身的元信息。
典型场景:校验配置中硬编码的路径是否被用户创建了符号链接;或者排查 symlink 是否被意外删除(link 文件没了,不是它指向的东西没了)。
- 用
os.Lstat后,仍需用os.IsNotExist判断链接是否存在 -
fi.Mode()&os.ModeSymlink != 0可确认它是符号链接,但不等于“链接有效”——有效性需额外os.Stat目标路径 - 不要混用
Lstat和Stat做同一路径的多次判断,容易逻辑混乱
避免竞态条件:不要用 os.Stat + 后续操作组合
写成 “先 os.Stat 判断存在 → 再 os.Open” 是经典竞态(TOCTOU):两次调用之间路径可能被删/改权限。这在多进程或临时目录场景下极易出错,错误信息常是 "no such file or directory" 或 "permission denied",但前一步 Stat 还成功了。
安全写法永远是:直接执行目标操作(如 os.Open、os.ReadFile),然后根据错误类型分支处理。
- 想读文件?直接
os.ReadFile(path),检查os.IsNotExist(err)或os.IsPermission(err) - 想创建文件但不覆盖?用
os.OpenFile(path, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0644),失败时再看是不是os.IsExist(err) - 仅“判断存在”本身无副作用,但一旦带后续动作,就该放弃独立判断函数
封装建议:按语义命名,不叫 Exists
如果你真需要一个工具函数,别叫 Exists —— 它含义模糊(存在文件?目录?链接?)。更清晰的命名如 PathExists(只管路径存在性)、IsRegularFile、IsDir,并在文档里写明行为边界。
示例 PathExists 实现:
func PathExists(path string) (bool, error) {
_, err := os.Stat(path)
if err == nil {
return true, nil
}
if os.IsNotExist(err) {
return false, nil
}
return false, err // 其他错误(如权限)不掩盖,由调用方决定如何处理
}
注意这个函数返回 error 不是只为“不存在”,而是为了暴露真实问题——比如 NFS 挂载点断开时,Stat 可能卡住或返回 I/O 错误,这时你得知道,而不是简单当“不存在”。
真正难的从来不是写个 if err == nil,而是想清楚“存在”对你当前业务意味着什么:是可读?可执行?还是仅仅路径没被 rm -rf 掉?这点不厘清,函数接口就注定要返工。

















