os.remove只能删文件或空目录,因设计为单路径原子删除,遇非空目录直接报enotempty错误;os.removeall递归删除整个目录树,但无回滚、不跳过错误,路径错误将导致不可逆误删。

os.Remove 只能删文件或空目录,传非空目录必报错;想删整个目录树,必须用 os.RemoveAll,但后者没回滚、不跳过错误、路径写错就真删了。
os.Remove 为什么删不了目录?
因为它的设计就是「单路径原子删除」:目标是文件 → 删掉;是空目录 → 删掉;是非空目录 → 直接返回 directory not empty 错误(Linux/macOS)或 ERROR_DIR_NOT_EMPTY(Windows)。它根本不会往下遍历子项。
- 常见误操作:
os.Remove("build/")想删构建目录,结果报syscall.ENOTEMPTY—— 因为 build/ 下有 .js 文件 - 末尾斜杠不影响判断:
os.Remove("tmp")和os.Remove("tmp/")行为完全一致 - 它不关心权限或占用:Linux 下可能删成功但句柄还挂着,Windows 下直接
Access is denied
os.RemoveAll 删除失败的三个典型原因
它会一层层往下删,遇到第一个失败就停,前面删掉的不会恢复。最常卡在:
-
文件被占用:Windows 上日志文件正被进程写入,
RemoveAll报The process cannot access the file because it is being used by another process -
只读文件阻塞:比如
.git/下的文件默认只读,RemoveAll遇到permission denied就中止 -
路径越界:传了
".."或相对路径未校验,os.RemoveAll("../dist")可能误删上层项目
解决办法不是硬 retry,而是删前先 os.Chmod(path, 0755) 改权限,或对 Windows 做重试逻辑(最多 3 次,间隔 100ms)。
怎么安全地判断该用 Remove 还是 RemoveAll?
别猜,用 os.Stat 查类型:
- 如果
fi.IsDir() == true且fi.Size() == 0→ 可用os.Remove - 如果
fi.IsDir() == true且子项数 > 0 → 必须用os.RemoveAll - 如果
os.IsNotExist(err)→ 两种函数都可跳过,不用报错
注意:os.Stat 本身也会因权限不足失败,所以得先处理 os.IsPermission,再判断是否存在。
生产环境删目录前必须做的三件事
os.RemoveAll 没有安全模式,全靠你兜底:
- 用
filepath.Abs转成绝对路径,避免相对路径歧义 - 加白名单校验:
strings.HasPrefix(absPath, "/tmp/") || strings.HasSuffix(absPath, "/cache"),拒绝/etc、/usr类路径 - 关键路径加日志:
log.Printf("removing %s", absPath),出问题时能快速定位
跨平台尤其要注意:Windows 下符号链接(symlink)会被当普通文件删,而 Linux/macOS 的行为一致——只删 link 本身,不碰 target。这点容易被忽略,导致清理后残留数据。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











