最简单方式是用os.AppendFile(Go 1.16+),但需注意其等价于OpenFile+Write+Close,不自动换行;手动追加须用os.O_APPEND|os.O_WRONLY(或O_RDWR)组合,配合O_CREATE处理不存在文件,且Linux/macOS下多goroutine并发写同一文件时内核保证单次Write原子性,Windows建议加锁;权限、路径及文件锁问题常导致失败。

用 os.OpenFile 以 os.O_APPEND | os.O_WRONLY 模式打开文件
直接调用 os.AppendFile(Go 1.16+)最简单,但很多旧项目或需要复用文件句柄的场景仍得手动控制。关键不是“先读再写”,而是跳过定位步骤,让系统自动把写入点锚定在末尾。
错误做法是用 os.O_WRONLY 打开后手动 Seek(0, io.SeekEnd) —— 这在多进程/多 goroutine 写同一文件时可能被覆盖,os.O_APPEND 才是内核级原子追加保障。
-
os.O_APPEND必须和os.O_WRONLY或os.O_RDWR同时使用,单独用会报invalid argument - 如果文件不存在,需额外加
os.O_CREATE,否则打开失败 - Windows 下对已映射的文件(如被其他程序独占打开)可能报
The process cannot access the file,此时需检查文件锁
注意 os.AppendFile 的隐含行为
os.AppendFile 是封装好的快捷函数,内部等价于 os.OpenFile(..., os.O_APPEND|os.O_CREATE|os.O_WRONLY) + Write + Close。它方便,但不适合高频小写入——每次调用都涉及系统调用开销和文件打开/关闭。
- 连续追加多行?别反复调用
os.AppendFile,改用单次os.OpenFile获取*os.File,然后用fmt.Fprintln或file.Write - 它不保证换行:传入
"hello"就写"hello",不会自动加\n,需自己处理格式 - Go 1.15 及更早版本没有这个函数,强行使用会编译失败
并发写入同一个文件要加锁吗?
Linux/macOS 下,只要所有写入都走 os.O_APPEND 模式,内核保证每次 write() 系统调用的原子性(即不会被截断或交错),无需额外互斥。但这是对「单次 Write 调用」而言。
立即学习“go语言免费学习笔记(深入)”;
- 如果你写的是
fmt.Fprintf(file, "%s: %v\n", k, v),而k和v很长,底层可能拆成多次write(),这时就可能和其他 goroutine 的写入穿插 - 安全做法:用
sync.Mutex包裹整个写操作,或者把待写内容拼成一个[]byte再单次Write - Windows 行为不一致:某些版本下
O_APPEND不完全可靠,生产环境建议统一加锁
权限与路径问题常被忽略
追加失败经常不是代码逻辑问题,而是运行时权限或路径语义导致的。
- 目标路径是相对路径时,基于当前工作目录(
os.Getwd()),不是源文件所在目录 - Linux 下若文件只有读权限(
-r--r--r--),即使有O_APPEND也会报permission denied - 父目录不可写(
chmod -w)会导致O_CREATE失败,哪怕文件已存在 - 用
os.IsPermission(err)或os.IsNotExist(err)显式判断错误类型,比字符串匹配更可靠
file, err := os.OpenFile("log.txt", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
log.Fatal(err)
}
defer file.Close()
if _, err := file.WriteString("new entry\n"); err != nil {
log.Fatal(err)
}
追加本身不难,难的是在不同系统、并发模型和权限边界下保持行为稳定。尤其当文件被外部进程监控(比如 tail -f)时,O_APPEND 的原子性是唯一能依赖的底线。


















