必须显式构造 tar.header 并调用 tw.writeheader(),否则解包后权限丢失、路径错乱、无法识别为普通文件;tar.writer 必须 close 才能生成合法归档,末尾需两个全零块,漏掉会导致错误或解压失败。

必须显式构造 tar.Header 并调用 tw.WriteHeader(),否则写入的文件在解包后权限丢失、路径错乱、甚至无法识别为普通文件。
tar.Writer 必须 close 才能生成合法归档
Go 的 tar.Writer 内部使用缓冲,tw.Close() 不仅刷新剩余数据,还会在归档末尾写入两个全零的 512 字节块(POSIX TAR 标准要求)。漏掉这步会导致:tar: Archive contains obsolescent base-64 headers 或 Unexpected EOF 等错误,Linux tar -xf 会失败,macOS 可能静默跳过末尾文件。
- 务必用
defer tw.Close(),且确保它在所有tw.WriteHeader()和io.Copy()之后执行 - 不要在循环中提前
tw.Close()—— 每个tar.Writer对应一个完整归档,不是每个文件一个 writer - 若需中途终止,应先
tw.Flush()再tw.Close(),避免残留脏数据
用 tar.FileInfoHeader() 保留权限和元信息
手动 new tar.Header 极易遗漏 Mode、ModTime、Typeflag,导致解压后文件变成 ----------(000 权限),或目录被当成普通文件写入。
- 正确做法:用
os.Stat()获取os.FileInfo,再传给tar.FileInfoHeader(fi, "") -
hdr.Name必须手动设置为相对路径(如"config/app.yaml"),不能依赖fi.Name()(它只返回 basename) - 对符号链接,第二个参数传 link target;对硬链接,需额外处理
Linkname和Typeflag - Windows 上注意路径分隔符:统一用
filepath.ToSlash()转成/,避免\导致解包失败
写入文件内容必须用 io.Copy,禁止 ioutil.ReadFile
对大于几 MB 的文件,ioutil.ReadFile() 会把整个文件加载进内存,极易触发 OOM。而 tar.Writer 支持流式写入,性能无损且内存恒定。
- 打开源文件后,直接
io.Copy(tw, srcFile),无需中间[]byte缓冲 - 写完内容后无需额外操作 ——
io.Copy返回实际字节数,与hdr.Size匹配即可 - 若源文件是 pipe 或 network stream,仍可照常工作;但注意
srcFile.Stat().Size()需提前获取,不能依赖io.Copy结果
遍历目录时要处理 . 和 ..,并跳过 socket/device 文件
filepath.Walk() 默认包含当前目录 . 和父目录 ..,且可能遇到 Unix domain socket、设备文件等非标准类型,tar.FileInfoHeader() 无法正确推导 Typeflag,导致解包出错。
- 在 walk 函数中用
strings.HasPrefix(path, ".")过滤隐藏文件(按需) - 检查
fi.Mode() & os.ModeType,跳过os.ModeSocket、os.ModeDevice、os.ModeNamedPipe - 对目录,
tar.FileInfoHeader()自动设Typeflag = tar.TypeDir;但需确保hdr.Name以/结尾(如"logs/"),否则某些 tar 工具会拒绝创建 - 遇到
os.ErrPermission应记录警告而非 panic,避免因单个文件权限问题中断整个打包
最易被忽略的是:即使你填了 hdr.Mode = 0755,如果没调用 tw.WriteHeader(),后续 io.Copy() 写入的内容也不会关联到该 header —— tar 格式靠 header/data 成对出现定义文件,header 缺失等于整个文件条目无效。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











