Go中tar.gz压缩解压需手动组合archive/tar与gzip.Writer/Reader,关键在路径处理、目录显式创建、流式读写、安全校验及硬链接/设备文件等边界处理。

tar.gz 压缩:用 archive/tar + gzip.Writer 流式写入
Go 标准库不提供一键压缩函数,必须手动组合 archive/tar 和 gzip.Writer。关键不是“怎么调用”,而是“文件头怎么写、路径怎么处理、空目录怎么带进去”。
常见错误:TarHeader.Name 以 / 开头会失败(POSIX tar 规范禁止绝对路径),os.Stat 返回的 ModeDir 没设 Typeflag 导致目录被跳过。
- 遍历文件时用
filepath.WalkDir(比Walk更可控,避免 symlink 循环) -
TarHeader.Name必须是相对路径,用strings.TrimPrefix(path, root)清洗 - 目录项要显式创建:
header.Typeflag = tar.TypeDir,且header.Size = 0 - 别直接
Write文件内容——大文件会爆内存,要用io.Copy(tarWriter, file)
tar.gz 解压:gzip.Reader 套 archive/tar 读取流
解压失败大多卡在两层 Reader 的衔接上:没检查 gzip.Header 是否存在、没跳过 tar 中的 padding block、对 .. 路径没过滤导致目录穿越。
真实场景里,你拿到的往往是个 *os.File 或 io.ReadCloser,不是内存字节切片——所以不能先 bytes.NewReader 再解压。
立即学习“go语言免费学习笔记(深入)”;
- 先用
gzip.NewReader包一层,再传给tar.NewReader - 遍历
tar.Reader时,用header.Name做白名单校验:!strings.HasPrefix(header.Name, "..") && !filepath.IsAbs(header.Name) - 写文件前必须
os.MkdirAll(filepath.Dir(dstPath), 0755),否则目录不存在会open /a/b/c.txt: no such file or directory - 注意
header.Typeflag == tar.TypeReg才写内容,TypeDir只建目录,TypeSymlink要额外处理
为什么不用 os/exec 调 tar -czf?
本地开发可能快,但上线后容易出兼容问题:容器里没 tar 命令、PATH 不一致、权限限制不让 fork、Windows 下根本跑不动。
更隐蔽的坑是信号和超时:子进程卡住时,cmd.Wait() 会阻塞整个 goroutine;而标准库流式处理可配合 context.WithTimeout 精确控制。
- 跨平台一致性:同一份代码在 Linux/macOS/Windows 容器里行为一致
- 内存可控:流式处理不依赖临时磁盘,适合低配环境或高频小文件归档
- 错误粒度细:能区分“gzip 校验失败”和“tar header 损坏”,而不是笼统的
exit status 2
流式处理中容易漏掉的边界:硬链接、设备文件、UID/GID
生产环境 tar 包常含硬链接(如 Go SDK 的 src 目录)、/dev/null 这类设备节点。标准库默认忽略它们,但你不显式处理,解压后就少东西。
tar.Header 有 Linkname 字段,但只在 Typeflag == tar.TypeLink 或 TypeSymlink 时有效;TypeCharDev/TypeBlock 需要 syscall.Mknod(仅 Linux)。
- 硬链接:记录首次出现的
header.Name→header.Linkname映射,后续遇到相同Linkname就用os.Link - 设备文件:检查
header.Typeflag,用syscall.Mknod(dst, header.Mode|syscall.S_IFCHR, int(unix.Mkdev(uint32(header.Devmajor), uint32(header.Devminor)))) - UID/GID:普通用户进程无法设置,除非加
setuid或用chown,多数场景直接忽略更安全
这些细节不会报错,但会导致解压结果和原始结构不一致——尤其是做镜像构建或配置分发时,差一个硬链接就可能让程序启动失败。










