跳转到主内容
趣航编程网 - 趣学编程,启航技术之路!

Golang如何压缩和解压tar.gz_Golang tar.gz操作教程【完整】

tar.gz 压缩解压必须严格遵循 gzip.NewReader→tar.NewReader(读)和 tar.NewWriter→gzip.NewWriter→os.Create(写)的嵌套顺序,且 Typeflag 必须显式设置、Size 必须用 io.CopyN 严格读取、路径需 clean 并校验防止遍历。 tar.gz 压缩必须先 tar 再 gzip,顺序反了就解不开 Go 里没有“一键 tar.gz”函数,
archive/tar
和
compress/gzip
是两个独立职责的包:tar 负责打包(合并多个文件+路径结构),gzip 负责压缩(单流字节压缩)。你不能用
gzip.NewWriter
包住一个
tar.Writer
的输出流以外的东西,也不能把未压缩的
*os.File
直接丢给
tar.NewReader
—— 那读出来全是乱码。 错误写法:
tar.NewReader(f)
(
f
是 .tar.gz 文件)→ 报错
archive/tar: invalid tar header
或解出空名、零大小文件 正确嵌套:
gzip.NewReader(f)
→ 得到
io.Reader
→ 传给
tar.NewReader()
写入时也得严格嵌套:
os.Create
→
gzip.NewWriter
→
tar.NewWriter
,且
tar.Writer
必须在
gzip.Writer
关闭前
Close()
漏掉
tw.Close()
或
gw.Close()
,生成的 .tar.gz 用
gunzip -t
检查会报
unexpected end of file
写 tar header 不设
Typeflag
就算内容对也解不开
tar.Header
的
Typeflag
字段不是可选的,Go 的
tar.Reader
对它做严格校验。默认值是
0
(即
\x00
),而 GNU tar 会容忍并尝试推测,但 Go 不会——它直接跳过该条目或报错
archive/tar: invalid header
。 普通文件必须显式写:
hdr.Typeflag = tar.TypeReg
目录必须写:
hdr.Typeflag = tar.TypeDir
,且
Name
末尾带
/
(如
"logs/"
),否则会被当成普通文件处理 别依赖
file.Stat().Mode()
自动推断类型;
Mode
控制权限,
Typeflag
控制条目语义 示例:
hdr := &tar.Header{Name: "config.json", Size: 128, Mode: 0644, Typeflag: tar.TypeReg}
解压 tar.gz 时不按
Header.Size
读就会串位
tar.Reader.Next()
返回一个 header 后,后续数据就是这个文件的内容,长度正好是
header.Size
。如果你用
io.Copy(dest, tr)
一直读到
io.EOF
,就会把下一个文件的 header 也吞进去,导致后续
Next()
解析失败、文件名错乱、甚至 panic。 安全做法:用
io.CopyN(dest, tr, header.Size)
,它保证只读指定字节数 千万别用
io.ReadAll(tr)
或循环
tr.Read(buf)
直到
err == io.EOF
—— 这破坏 tar 流结构 目录条目(
TypeDir
)的
Size
是 0,此时跳过
CopyN
,只调
os.MkdirAll(...)
如果要限速或打日志,套
io.LimitReader(tr, header.Size)
再传给
io.Copy
,而不是自己管理缓冲 路径遍历漏洞不校验,
../../etc/passwd
就真写进系统了 tar 和 zip 一样,
Header.Name
完全由打包方控制。Go 标准库不做任何路径 sanitize,
filepath.Join(dstRoot, header.Name)
一旦遇到
"../"
,就可能覆盖任意位置。这不是理论风险,而是真实 CVE 场景。 立即学习 “ go语言免费学习笔记(深入) ”; 必须先
cleanPath := filepath.Clean(header.Name)
再检查:
cleanPath != header.Name || strings.HasPrefix(cleanPath, "..") || strings.HasPrefix(cleanPath, "/")
→ 直接跳过该条目 创建文件前,用
os.MkdirAll(filepath.Dir(dstPath), 0755)
确保父目录存在;目录条目要单独判断
header.Typeflag == tar.TypeDir
并
os.MkdirAll(dstPath, header.Mode)
Windows 路径分隔符也要处理:
filepath.ToSlash(header.Name)
统一后校验,避免
"..\foo"
绕过 最常被忽略的一点是:所有
Close()
都不能靠
defer
在同一函数里混用读写句柄——比如写完
.tar.gz
立刻用
os.Open
读它,此时
defer gw.Close()
还没执行,文件内容可能还在内核缓冲里。分阶段、显式
Close()
,才是流式归档稳住的关键。

相关文章