遇到一个问题:用 golang 将一个文件夹打包为 tar.gz ,在 UOS 中用默认应用打开后,会显示有同名的目录、文档两项,实际上只应该有一项目录。借此研究了一下 tar 的打包格式。
1. 格式介绍
tar.gz
格式是 linux 环境中常用的文档打包格式,事实上这种格式是两个步骤的组合:先将给定的文档打包为一个大的 tar 文档,然后用 gzip 算法将这个文档进行压缩。当然,第二步骤的压缩算法可以替换,产生的文件自然就是 tar.xz
等等。至于 windows 中常见的 zip
、rar
格式,则没有把打包和压缩这两个步骤区分得如此明显。
tar.gz
的第一步发源自磁带存储的时代,tar 即 tarball,当时的存储设备(磁带机)无法很好地处理变长数据块写入,原因是[1]:
- 有的磁带机写入变长数据块时会留下大量空白区间(用于磁带的物理启动和停止);
- 有的磁带机仅支持固定长度的数据写入;
- 写入数据时(包括写入文件系统或者网络数据流),写入一个较大的数据块总是比写入许多较小的数据块更节省时间(避免了每次写入数据前的准备工作和写入完成后的收尾工作)。 因此,将许多小文件打包成一整个大文件后再写入存储设备,在磁带机的时代是必不可少的工作。
至于压缩算法,gzip 是 deflate 算法的一种变体压缩算法,后者又是变种 LZ77 算法(一种基于滑动窗口实现的字符串替换压缩方法,详见[2]) 和 哈夫曼编码的组合。
不难注意到,tar.gz
文件的生成过程也很好地体现了 Unix 哲学 “Do One Thing and Do It Well”。
考虑到压缩的过程是相对确定的,我们理应更加关注将多个文件打包为 tar 包时的处理过程,先简单了解一下 tar 文件的结构[3],一个 tar 包的结构总是:
-----------------------------------------
| header | body ... | header | body ... |
-----------------------------------------
其中 Header 的结构定义为:
struct posix_header
{ /* byte offset */
char name[100]; /* 0 */
char mode[8]; /* 100 */
char uid[8]; /* 108 */
char gid[8]; /* 116 */
char size[12]; /* 124 */
char mtime[12]; /* 136 */
char chksum[8]; /* 148 */
char typeflag; /* 156 */
char linkname[100]; /* 157 */
char magic[6]; /* 257 */
char version[2]; /* 263 */
char uname[32]; /* 265 */
char gname[32]; /* 297 */
char devmajor[8]; /* 329 */
char devminor[8]; /* 337 */
char prefix[155]; /* 345 */
/* 500 */
};
注:
- 当文件的名称太长时,文件的名称可以作为一个单独的 header + body 结构存入 tar 文件中;
- header 会被填充到 512 byte 长度。
2. 压缩包生成代码
网上可以找到很多 golang 使用 archive/tar
和 compress/gzip
打包文件夹的 demo,现在也可以让大语言模型帮我们写一个,并且为了便于理解,做简单修改:
package main
import (
"archive/tar"
"fmt"
"io"
"os"
"path/filepath"
)
func main() {
if len(os.Args) != 3 {
fmt.Println("Useage: myTar srcFolder targetFileName")
os.Exit(1)
}
// 指定要打包的文件夹路径
source := os.Args[1]
// 指定打包后的文件名
target := os.Args[2]
// 创建一个新的 tar 文件
tarfile, err := os.Create(target)
if err != nil {
fmt.Println(err)
return
}
defer tarfile.Close()
// 创建一个 gzip.Writer
// gzipWriter := gzip.NewWriter(tarfile)
// defer gzipWriter.Close()
// 创建一个 tar.Writer
tarWriter := tar.NewWriter(tarfile)
defer tarWriter.Close()
// 遍历文件夹,将文件写入 tar 文件
filepath.Walk(source, func(path string, info os.FileInfo, err error) error {
fmt.Printf("path: %v\n", path)
if err != nil {
return err
}
// 获取文件信息
header, err := tar.FileInfoHeader(info, "")
if err != nil {
return err
}
fmt.Printf("Header.Name: %v\n", header.Name)
// 写入文件信息
if err := tarWriter.WriteHeader(header); err != nil {
return err
}
// 如果是文件,写入文件内容
if !info.IsDir() {
file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close()
if _, err := io.Copy(tarWriter, file); err != nil {
return err
}
}
return nil
})
fmt.Println("Tarball created successfully!")
}
3. 程序运行结果分析
3.1 windows 版本 bug fix
将上面的程序编译:go build -o mytar.exe main.go
,并且将如下结构的目录打包,.\mytar.exe data\ example.tar
data ├── 2.log └── target └── 1.log
在 windows 上用二进制文件查看工具,或者在 linux 中用 xxd
工具查看:xxd example.tar | vim -
,可以得到如下输出:
00000000: 6461 7461 2f00 0000 0000 0000 0000 0000 data/...........
00000010: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000020: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000030: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000040: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000050: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000060: 0000 0000 3030 3030 3737 3700 3030 3030 ....0000777.0000
00000070: 3030 3000 3030 3030 3030 3000 3030 3030 000.0000000.0000
00000080: 3030 3030 3030 3000 3134 3533 3136 3531 0000000.14531651
00000090: 3430 3700 3031 3031 3637 0020 3500 0000 407.010167. 5...
000000a0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000000b0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000000c0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000000d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000000e0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000000f0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000100: 0075 7374 6172 0030 3000 0000 0000 0000 .ustar.00.......
00000110: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000120: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000130: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000140: 0000 0000 0000 0000 0030 3030 3030 3030 .........0000000
00000150: 0030 3030 3030 3030 0000 0000 0000 0000 .0000000........
00000160: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000170: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000180: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000190: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000001a0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000001b0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000001c0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000001d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000001e0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000001f0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000200: 322e 6c6f 6700 0000 0000 0000 0000 0000 2.log...........
00000210: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000220: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000230: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000240: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000250: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000260: 0000 0000 3030 3030 3636 3600 3030 3030 ....0000666.0000
00000270: 3030 3000 3030 3030 3030 3000 3030 3030 000.0000000.0000
00000280: 3030 3030 3035 3300 3134 3533 3136 3531 0000053.14531651
00000290: 3432 3000 3031 3031 3133 0020 3000 0000 420.010113. 0...
000002a0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000002b0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000002c0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000002d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000002e0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000002f0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000300: 0075 7374 6172 0030 3000 0000 0000 0000 .ustar.00.......
00000310: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000320: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000330: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000340: 0000 0000 0000 0000 0030 3030 3030 3030 .........0000000
00000350: 0030 3030 3030 3030 0000 0000 0000 0000 .0000000........
00000360: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000370: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000380: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000390: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000003a0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000003b0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000003c0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000003d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000003e0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000003f0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000400: 3230 3233 2d31 312d 3239 2032 313a 3236 2023-11-29 21:26
00000410: 3a33 3020 5468 6973 2069 7320 6120 6465 :30 This is a de
00000420: 6d6f 206f 6620 6c6f 6720 3200 0000 0000 mo of log 2.....
...
00000600: 7461 7267 6574 2f00 0000 0000 0000 0000 target/.........
00000610: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000620: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000630: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000640: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000650: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000660: 0000 0000 3030 3030 3737 3700 3030 3030 ....0000777.0000
00000670: 3030 3000 3030 3030 3030 3000 3030 3030 000.0000000.0000
00000680: 3030 3030 3030 3000 3134 3533 3136 3336 0000000.14531636
00000690: 3031 3100 3031 3035 3336 0020 3500 0000 011.010536. 5...
000006a0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000006b0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000006c0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000006d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000006e0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000006f0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000700: 0075 7374 6172 0030 3000 0000 0000 0000 .ustar.00.......
00000710: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000720: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000730: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000740: 0000 0000 0000 0000 0030 3030 3030 3030 .........0000000
00000750: 0030 3030 3030 3030 0000 0000 0000 0000 .0000000........
00000760: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000770: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000780: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000790: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000007a0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000007b0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000007c0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000007d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000007e0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000007f0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000800: 312e 6c6f 6700 0000 0000 0000 0000 0000 1.log...........
00000810: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000820: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000830: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000840: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000850: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000860: 0000 0000 3030 3030 3636 3600 3030 3030 ....0000666.0000
00000870: 3030 3000 3030 3030 3030 3000 3030 3030 000.0000000.0000
00000880: 3030 3030 3035 3100 3134 3533 3136 3336 0000051.14531636
00000890: 3035 3500 3031 3031 3137 0020 3000 0000 055.010117. 0...
000008a0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000008b0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000008c0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000008d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000008e0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000008f0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000900: 0075 7374 6172 0030 3000 0000 0000 0000 .ustar.00.......
00000910: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000920: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000930: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000940: 0000 0000 0000 0000 0030 3030 3030 3030 .........0000000
00000950: 0030 3030 3030 3030 0000 0000 0000 0000 .0000000........
00000960: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000970: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000980: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000990: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000009a0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000009b0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000009c0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000009d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000009e0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000009f0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000a00: 3230 3233 2d31 312d 3239 2032 313a 3236 2023-11-29 21:26
00000a10: 3a33 3020 5468 6973 2069 7320 6120 6465 :30 This is a de
00000a20: 6d6f 206f 6620 6c6f 6700 0000 0000 0000 mo of log.......
...
00000ff0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
直接打开该打包文件,或者从 header 的 name 字段也可以发现,这里失去了原本的层级结构,所有的文件都被提取到了最上层,换句话说,实际写入 tar 文件的 header.name 字段,仅仅是原本文件路径的最后一段。
不难定位到是 header, err := tar.FileInfoHeader(info, "")
的问题,这个函数将文件名填入到 header.name 字段中,因此需要写入完整的相对路径:
header.Name = filepath.ToSlash(path)
3.2 改进:更灵活的输入
对于上面的程序,在 windows 中输入的文件夹路径必须为形如 data\
格式,如果指定了 .\data\
作为输入,那么压缩后的文件就会包含 .\data
和 data
两个目录,前者必然是空目录。
为了解决这个问题,需要对输入的路径做清洗:
source := filepath.Clean(os.Args[1])
为了支持更自由的路径,再修改写入的 header.name 字段:
dir := filepath.Base(source)
header.Name = filepath.ToSlash(
filepath.Join(dir, strings.TrimPrefix(path, source)))
3.3 再次改进:bug fix
使用 linux 自带的 tar 工具打包,同样查看二进制内容,结果如下:
00000000: 6461 7461 2f00 0000 0000 0000 0000 0000 data/...........
00000010: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000020: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000030: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000040: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000050: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000060: 0000 0000 3030 3030 3737 3700 3030 3031 ....0000777.0001
00000070: 3735 3000 3030 3031 3735 3000 3030 3030 750.0001750.0000
00000080: 3030 3030 3030 3000 3134 3533 3136 3531 0000000.14531651
00000090: 3430 3700 3031 3037 3333 0020 3500 0000 407.010733. 5...
000000a0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000000b0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000000c0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000000d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000000e0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000000f0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000100: 0075 7374 6172 2020 0063 6865 6e67 0000 .ustar .cheng..
00000110: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000120: 0000 0000 0000 0000 0063 6865 6e67 0000 .........cheng..
00000130: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000140: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000150: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000160: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000170: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000180: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000190: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000001a0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000001b0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000001c0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000001d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000001e0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000001f0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000200: 6461 7461 2f32 2e6c 6f67 0000 0000 0000 data/2.log......
00000210: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000220: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000230: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000240: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000250: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000260: 0000 0000 3030 3030 3737 3700 3030 3031 ....0000777.0001
00000270: 3735 3000 3030 3031 3735 3000 3030 3030 750.0001750.0000
00000280: 3030 3030 3035 3300 3134 3533 3136 3531 0000053.14531651
00000290: 3432 3000 3031 3135 3733 0020 3000 0000 420.011573. 0...
000002a0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000002b0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000002c0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000002d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000002e0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000002f0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000300: 0075 7374 6172 2020 0063 6865 6e67 0000 .ustar .cheng..
00000310: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000320: 0000 0000 0000 0000 0063 6865 6e67 0000 .........cheng..
00000330: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000340: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000350: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000360: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000370: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000380: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000390: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000003a0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000003b0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000003c0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000003d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000003e0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000003f0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000400: 3230 3233 2d31 312d 3239 2032 313a 3236 2023-11-29 21:26
00000410: 3a33 3020 5468 6973 2069 7320 6120 6465 :30 This is a de
00000420: 6d6f 206f 6620 6c6f 6720 3200 0000 0000 mo of log 2.....
...
00000600: 6461 7461 2f74 6172 6765 742f 0000 0000 data/target/....
00000610: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000620: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000630: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000640: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000650: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000660: 0000 0000 3030 3030 3737 3700 3030 3031 ....0000777.0001
00000670: 3735 3000 3030 3031 3735 3000 3030 3030 750.0001750.0000
00000680: 3030 3030 3030 3000 3134 3533 3136 3336 0000000.14531636
00000690: 3031 3000 3031 3232 3132 0020 3500 0000 010.012212. 5...
000006a0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000006b0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000006c0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000006d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000006e0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000006f0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000700: 0075 7374 6172 2020 0063 6865 6e67 0000 .ustar .cheng..
00000710: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000720: 0000 0000 0000 0000 0063 6865 6e67 0000 .........cheng..
00000730: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000740: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000750: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000760: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000770: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000780: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000790: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000007a0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000007b0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000007c0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000007d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000007e0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000007f0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000800: 6461 7461 2f74 6172 6765 742f 312e 6c6f data/target/1.lo
00000810: 6700 0000 0000 0000 0000 0000 0000 0000 g...............
00000820: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000830: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000840: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000850: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000860: 0000 0000 3030 3030 3737 3700 3030 3031 ....0000777.0001
00000870: 3735 3000 3030 3031 3735 3000 3030 3030 750.0001750.0000
00000880: 3030 3030 3035 3100 3134 3533 3136 3336 0000051.14531636
00000890: 3035 3400 3031 3330 3634 0020 3000 0000 054.013064. 0...
000008a0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000008b0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000008c0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000008d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000008e0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000008f0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000900: 0075 7374 6172 2020 0063 6865 6e67 0000 .ustar .cheng..
00000910: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000920: 0000 0000 0000 0000 0063 6865 6e67 0000 .........cheng..
00000930: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000940: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000950: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000960: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000970: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000980: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000990: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000009a0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000009b0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000009c0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000009d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000009e0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000009f0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000a00: 3230 3233 2d31 312d 3239 2032 313a 3236 2023-11-29 21:26
00000a10: 3a33 3020 5468 6973 2069 7320 6120 6465 :30 This is a de
00000a20: 6d6f 206f 6620 6c6f 6700 0000 0000 0000 mo of log.......
...
000027f0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
按照第一节中的 header 结构逐字节进行对比,最后发现,区别在于文件夹所标记的 name 字段。tar 工具生成的文件中,文件夹是以 ‘/’ 结尾的,而 golang 的 compress/tar 库生成的文件则不包含最后的 ‘/’ 字符,导致这种情况的原因是 filepath 对路径进行处理时,会清除掉路径最后的 ‘/’ 字符。如果打开该打包文件的工具通过 header.typeflag 字段判断文件类型,当然不会有问题,但是如果通过文件名称最后是否包含分隔符来判断是否为文件夹,则使用上述 golang 代码打包和使用 tar 工具打包,则必然会导致解压工具对文件夹的处理有歧义。
最终代码
一个最小的,可以将一个文件夹打包为 tar.gz 格式的 golang 样例代码应该是:
package main
import (
"archive/tar"
"compress/gzip"
"fmt"
"io"
"os"
"path/filepath"
"strings"
)
func main() {
if len(os.Args) != 3 {
fmt.Println("Useage: myTar srcFolder targetFileName")
os.Exit(1)
}
// 指定要打包的文件夹路径
source := filepath.Clean(os.Args[1])
dir := filepath.Base(source)
// 指定打包后的文件名
target := os.Args[2]
// 创建一个新的 tar 文件
tarfile, err := os.Create(target)
if err != nil {
fmt.Println(err)
return
}
defer tarfile.Close()
// 创建一个 gzip.Writer
gzipWriter := gzip.NewWriter(tarfile)
defer gzipWriter.Close()
// 创建一个 tar.Writer
tarWriter := tar.NewWriter(gzipWriter)
defer tarWriter.Close()
// 遍历文件夹,将文件写入 tar 文件
filepath.Walk(source, func(path string, info os.FileInfo, err error) error {
// fmt.Printf("path: %v\n", path)
if err != nil {
return err
}
// 获取文件信息
header, err := tar.FileInfoHeader(info, "")
if err != nil {
return err
}
// 重写 header.Name 字段
header.Name = filepath.ToSlash(filepath.Join(dir, strings.TrimPrefix(path, source)))
if info.IsDir() {
header.Name += "/"
}
fmt.Printf("Header.Name: %v\n", header.Name)
// 写入文件信息
if err := tarWriter.WriteHeader(header); err != nil {
return err
}
// 如果是文件,写入文件内容
if !info.IsDir() {
file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close()
if _, err := io.Copy(tarWriter, file); err != nil {
return err
}
}
return nil
})
fmt.Println("Tarball created successfully!")
}