go标准库compress/bzip2仅提供底层bzip2流编解码,不支持直接解压.bzip2文件,因其无法解析文件头(如4字节magic)、crc及size等元信息;需手动跳过头部或改用ebfe/bz2等第三方库。

compress/bzip2 不能直接解压 .bz2 文件
Go 标准库的 compress/bzip2 包只提供底层 bzip2 数据流编解码器,不封装文件头解析或 .bz2 文件格式(含 magic header、CRC、size 等元信息)。直接用它读取一个磁盘上的 .bz2 文件会 panic 或返回 io.ErrUnexpectedEOF —— 因为它期望输入是纯压缩数据流,而非带文件头的完整归档。
必须先剥离 .bz2 文件头再喂给 bzip2.NewReader
标准 .bz2 文件开头有 4 字节 magic(0x42, 0x5A, 0x68, 0x3[1-9]),后面紧跟着可选的 padding 和实际压缩数据。你需要跳过这些头部,把指针定位到第一个有效压缩块起始处。
- 最稳妥方式:用
io.Copy配合bzip2.NewReader,但前提是源io.Reader已跳过 magic 和版本字节 - 简单做法:用
bytes.NewReader(data[4:])(仅限内存小文件;注意:不是所有 .bz2 都严格 4 字节头,BZ2 偶尔含 padding,生产环境应解析 header) - 推荐做法:用第三方库如
github.com/klauspost/pgzip不适用;真正兼容的选github.com/dsnet/compress或手动解析 —— 但多数场景直接用系统bzcat更可靠
用 exec.Command 调用系统 bzcat 是更稳的选择
除非你明确需要纯 Go 实现或嵌入式环境无 shell,否则调用系统 bzcat(或 bunzip2 -c)几乎零出错,且自动处理所有 .bz2 变体(multi-stream、truncated 等)。
cmd := exec.Command("bzcat", "/path/to/file.bz2")
out, err := cmd.Output()
if err != nil {
// 检查 err.(*exec.ExitError).ExitCode() == 2 表示文件损坏
}
// out 就是解压后字节
- Windows 下需确保安装了
bzcat(例如通过 Cygwin、WSL 或单独二进制) - 若需流式处理大文件,用
cmd.StdoutPipe()接管道,避免内存全载 - 注意权限和路径注入风险:文件路径必须经
filepath.Clean()校验,不可拼接用户输入
想纯 Go?改用 github.com/ebfe/bz2
compress/bzip2 的设计初衷是作为底层 codec,Go 官方无意补全 .bz2 文件格式支持。社区已有成熟替代:github.com/ebfe/bz2 提供 bz2.NewReader 和 bz2.ReadCloser,内部自动识别并跳过 magic header,行为接近 gzip.NewReader。
- 安装:
go get github.com/ebfe/bz2 - 用法类似标准库:
reader := bz2.NewReader(file),之后io.Copy(dst, reader)即可 - 它不依赖 cgo,纯 Go 实现,支持并发解压(通过
bz2.NewReaderWithConcurrency) - 唯一限制:不支持带加密或非标准 extension 的 .bz2(这类本就极少见)
archive/tar,而 tar 包本身不关心压缩格式——只要传入的 io.Reader 是解压后的流就行。所以关键不是“怎么用 compress/bzip2”,而是“谁来负责剥掉那 4 个字节”。











