Go文件I/O错误均通过error返回而非panic,必须显式检查err是否为nil;recover无法捕获磁盘满、权限拒绝等系统调用错误,仅适用于空指针、越界等运行时异常。

os.WriteFile 和 file.Write 的错误不是 panic,别用 recover 拦截——它根本不会被触发。所有磁盘满、权限拒绝、路径不存在、Ctrl+C 中断,都走 err != nil 返回路径,必须显式判断。
为什么 recover 对文件写入失败完全无效
Go 的 panic 只由程序内部逻辑错误引发(如空指针解引用、切片越界、除零),而文件 I/O 失败属于系统调用层面的常规错误,全部通过返回 error 告知。比如:
write /tmp/log: no space left on deviceopen /etc/config.json: permission denied-
interrupted system call(来自 Ctrl+C 或kill -15)
这些都不会进入 panic 流程,defer func() { recover() }() 一句也捕获不到。强行套用只会掩盖真正该处理的 err 分支。
os.WriteFile 写入失败时该怎么处理
os.WriteFile 简洁但有明确边界:它覆盖写入、一次性加载全部数据到内存、不支持追加。出错时你只有一处 err 可检查:
立即学习“go语言免费学习笔记(深入)”;
- 用
os.IsNotExist(err)判断父目录是否缺失,必要时先os.MkdirAll(filepath.Dir(path), 0755) - 用
os.IsPermission(err)区分是权限问题还是只读文件系统 - 对磁盘满等临时性错误,可考虑重试(但需加限流和退避,避免打爆 CPU)
- 不要直接
log.Fatal(err)—— 日志里至少带上path和操作类型,例如:fmt.Printf("failed to write %s: %v", path, err)
需要持续写入时,如何安全响应 Ctrl+C
日志、导出、流式生成等场景不能靠 os.WriteFile,得用 os.OpenFile + 显式 file.Write,并监听信号主动退出:
- 在
go func()里用signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) - 收到信号后,优先调用
file.Close()(确保内核缓冲区 flush) - 不要在 signal handler 里做耗时操作(如重命名、压缩),只做资源释放
- 若写入中途被中断,且业务要求“原子性”,应先写临时文件,成功后再
os.Rename
注意:defer file.Close() 在主 goroutine 里依然要保留,但它无法替代信号感知——因为 Ctrl+C 默认杀进程,defer 不会执行。
bufio.Writer 写入后忘记 Flush 是常见静默失败点
用 bufio.NewWriter 提升性能时,Write 只写入缓冲区,不落盘。常见误操作:
- 只调
wr.Write([]byte(...))就结束,没调wr.Flush() -
defer wr.Flush()放错位置(比如 defer 在 open 失败分支之后) - 在
Flush()后忽略其返回的err—— 它可能暴露磁盘满或 broken pipe
正确姿势:每次写完关键数据(如一条日志)后立即 if err := wr.Flush(); err != nil { /* 处理 */ };或者用 io.WriteString(wr, ...) 配合显式 Flush。
真正的难点不在“怎么写”,而在“怎么确认它真的写进去了”。os.WriteFile 返回 nil 并不等于数据已刷到磁盘;file.Write 成功也不代表 Close() 能成功;Flush() 成功更不保证 fsync 完成。需要根据一致性要求,在性能与可靠性之间做取舍。


















