Go 语言标准库 —— archive/tar 包(tar 归档处理)
🔹 常量
文件类型标志总览
-
说明:tar 包定义了多种文件类型常量,用于标识不同类型的文件
-
所有文件类型:
- tar.TypeReg - 普通文件(‘0’ 或 ‘\x00’)
- tar.TypeDir - 目录(‘5’)
- tar.TypeSymlink - 符号链接(‘2’)
- tar.TypeLink - 硬链接(‘1’)
- tar.TypeChar - 字符设备(‘3’)
- tar.TypeBlock - 块设备(‘4’)
- tar.TypeFifo - FIFO 管道(‘6’)
- tar.TypeRegA - 旧版本普通文件(‘7’)
普通文件类型
tar.TypeReg
-
值:‘0’ 或 ‘\x00’,表示普通文件
-
说明:最常见的文件类型,用于表示常规数据文件
-
示例
package main import ( "archive/tar" "fmt" ) func main() { // 普通文件 fmt.Printf("TypeReg: %c\n", tar.TypeReg) // 创建普通文件头 header := &tar.Header{ Name: "file.txt", Mode: 0644, Size: 1024, Typeflag: tar.TypeReg, } fmt.Printf("创建普通文件:%s\n", header.Name) }
目录类型
tar.TypeDir
-
值:‘5’,表示目录
-
说明:用于表示目录结构,Size 通常为 0
-
注意事项:
- 目录的 Name 通常以 / 结尾
- 创建目录时 Size 应设置为 0
-
示例
package main import ( "archive/tar" "fmt" ) func main() { header := &tar.Header{ Name: "mydir/", Mode: 0755, Typeflag: tar.TypeDir, Size: 0, // 目录大小为 0 } fmt.Printf("创建目录:%s, 类型:%c\n", header.Name, header.Typeflag) }
符号链接类型
tar.TypeSymlink
-
值:‘2’,表示符号链接
-
说明:用于表示符号链接(软链接),需要设置 Linkname 字段
-
注意事项:
- 必须设置 Linkname 字段指向目标
- Size 通常为 0
-
示例
package main import ( "archive/tar" "fmt" ) func main() { header := &tar.Header{ Name: "link.txt", Typeflag: tar.TypeSymlink, Linkname: "target.txt", Size: 0, } fmt.Printf("符号链接:%s -> %s\n", header.Name, header.Linkname) }
硬链接类型
tar.TypeLink
-
值:‘1’,表示硬链接
-
说明:用于表示硬链接,Linkname 指向已存在的文件
-
注意事项:
- Linkname 必须是归档中已存在的文件
- 硬链接共享相同的 inode
-
示例
header := &tar.Header{ Name: "hardlink.txt", Typeflag: tar.TypeLink, Linkname: "original.txt", }
字符设备类型
tar.TypeChar
-
值:‘3’,表示字符设备
-
说明:用于表示字符设备文件(如 /dev/null)
-
注意事项:
- 需要设置 Devmajor 和 Devminor 字段
- 只在 Unix-like 系统上有意义
-
示例
header := &tar.Header{ Name: "dev/null", Typeflag: tar.TypeChar, Devmajor: 1, Devminor: 3, }
块设备类型
tar.TypeBlock
-
值:‘4’,表示块设备
-
说明:用于表示块设备文件(如硬盘分区)
-
注意事项:
- 需要设置 Devmajor 和 Devminor 字段
-
示例
header := &tar.Header{ Name: "dev/sda", Typeflag: tar.TypeBlock, Devmajor: 8, Devminor: 0, }
FIFO 管道类型
tar.TypeFifo
-
值:‘6’,表示 FIFO 管道
-
说明:用于表示命名管道(FIFO)
-
示例
header := &tar.Header{ Name: "mypipe", Typeflag: tar.TypeFifo, Mode: 0644, }
🔹 类型
tar.Header
tar.Header struct
-
说明:表示 tar 归档中的一个文件头,包含文件的元数据信息
-
字段详解:
- Name string - 文件名或路径(支持相对路径和绝对路径)
- Mode int64 - 权限模式(如 0644、0755)
- Uid int - 用户 ID
- Gid int - 组 ID
- Size int64 - 文件大小(字节)
- ModTime time.Time - 修改时间
- Typeflag byte - 文件类型(tar.TypeReg、tar.TypeDir 等)
- Linkname string - 链接目标(符号链接或硬链接)
- Uname string - 用户名(可选)
- Gname string - 组名(可选)
- Devmajor int64 - 设备主版本号(设备文件)
- Devminor int64 - 设备次版本号(设备文件)
-
注意事项:
- Name 字段应使用正斜杠(/)作为路径分隔符
- 对于目录,Name 通常以 / 结尾
- Size 字段对于目录和符号链接应该为 0
- ModTime 通常使用文件的最后修改时间
-
示例(完整)
package main import ( "archive/tar" "fmt" "time" ) func main() { // 创建完整的文件头 header := &tar.Header{ Name: "test.txt", Mode: 0644, Uid: 1000, Gid: 1000, Size: 1024, ModTime: time.Now(), Typeflag: tar.TypeReg, Uname: "user", Gname: "group", } fmt.Printf("文件:%s\n", header.Name) fmt.Printf("大小:%d 字节\n", header.Size) fmt.Printf("权限:%o\n", header.Mode) fmt.Printf("修改时间:%v\n", header.ModTime) fmt.Printf("类型:%c\n", header.Typeflag) } -
使用场景示例
-
创建普通文件头
- 示例:
header := &tar.Header{ Name: "file.txt", Mode: 0644, Size: int64(len(content)), Typeflag: tar.TypeReg, ModTime: time.Now(), }
- 示例:
-
创建目录头
- 示例:
header := &tar.Header{ Name: "mydir/", Mode: 0755, Typeflag: tar.TypeDir, ModTime: time.Now(), }
- 示例:
-
创建符号链接头
- 示例:
header := &tar.Header{ Name: "link.txt", Linkname: "target.txt", Typeflag: tar.TypeSymlink, ModTime: time.Now(), }
- 示例:
-
使用 FileInfoHeader 创建
- 示例:
fileInfo, _ := os.Stat("file.txt") header, _ := tar.FileInfoHeader(fileInfo, "") header.Name = "archive/path/file.txt"
- 示例:
-
tar.Reader
tar.Reader struct
-
说明:用于从 tar 归档读取数据
-
常用方法详解
-
Next 方法
- 说明:移动到归档中的下一个文件
- 方法:
Next() (*Header, error) - 返回值:
- Header:下一个文件的头信息
- error:错误(io.EOF 表示结束)
- 注意:每次调用 Next 后,才能读取该文件的内容
- 示例:
tr := tar.NewReader(file) for { header, err := tr.Next() if err == io.EOF { break // 读取完成 } if err != nil { return err } fmt.Println(header.Name) }
-
Read 方法
- 说明:读取当前文件的内容
- 方法:
Read(b []byte) (int, error) - 注意:必须先调用 Next 才能使用 Read
- 示例:
header, _ := tr.Next() buf := make([]byte, header.Size) tr.Read(buf)
-
使用 io.ReadAll 读取
- 说明:一次性读取整个文件内容
- 示例:
header, _ := tr.Next() content, _ := io.ReadAll(tr) fmt.Println(string(content))
-
-
示例(完整)
package main import ( "archive/tar" "fmt" "io" "os" ) func main() { // 打开 tar 文件 file, err := os.Open("archive.tar") if err != nil { fmt.Println("打开失败:", err) return } defer file.Close() // 创建读取器 tr := tar.NewReader(file) // 遍历归档 for { header, err := tr.Next() if err == io.EOF { break } if err != nil { fmt.Println("读取失败:", err) return } fmt.Printf("文件:%s, 大小:%d\n", header.Name, header.Size) } } -
使用场景示例
-
读取所有文件内容
- 示例:
tr := tar.NewReader(file) for { header, err := tr.Next() if err == io.EOF { break } content, _ := io.ReadAll(tr) fmt.Printf("%s: %s\n", header.Name, string(content)) }
- 示例:
-
跳过目录只读取文件
- 示例:
tr := tar.NewReader(file) for { header, err := tr.Next() if err == io.EOF { break } if header.Typeflag == tar.TypeDir { continue // 跳过目录 } // 处理文件 }
- 示例:
-
查找特定文件
- 示例:
tr := tar.NewReader(file) for { header, err := tr.Next() if err == io.EOF { break } if header.Name == "target.txt" { content, _ := io.ReadAll(tr) fmt.Println(string(content)) break } }
- 示例:
-
tar.Writer
tar.Writer struct
-
说明:用于向 tar 归档写入数据
-
常用方法详解
-
WriteHeader 方法
- 说明:写入文件头信息
- 方法:
WriteHeader(hdr *Header) error - 注意:
- 必须先写入文件头,才能写入文件内容
- 对于目录,Typeflag 应设置为 tar.TypeDir
- 对于符号链接,需要设置 Linkname 字段
- 示例:
tw := tar.NewWriter(file) header := &tar.Header{ Name: "file.txt", Mode: 0644, Size: int64(len(content)), } tw.WriteHeader(header)
-
Write 方法
- 说明:写入文件内容
- 方法:
Write(b []byte) (int, error) - 注意:
- 必须在 WriteHeader 之后调用
- 写入的字节数应该与 Header.Size 匹配
- 示例:
tw.WriteHeader(header) tw.Write([]byte(content))
-
Close 方法
- 说明:完成 tar 归档写入
- 方法:
Close() error - 注意:
- 必须调用 Close 来完成归档
- Close 会写入结束标记
- 应该在 defer 中调用确保关闭
- 示例:
tw := tar.NewWriter(file) defer tw.Close() // 写入文件...
-
-
示例(完整)
package main import ( "archive/tar" "fmt" "os" ) func main() { // 创建 tar 文件 file, err := os.Create("archive.tar") if err != nil { fmt.Println("创建失败:", err) return } defer file.Close() // 创建写入器 tw := tar.NewWriter(file) defer tw.Close() // 写入文件头 header := &tar.Header{ Name: "test.txt", Mode: 0644, Size: int64(len("hello world")), } if err := tw.WriteHeader(header); err != nil { fmt.Println("写入头失败:", err) return } // 写入内容 if _, err := tw.Write([]byte("hello world")); err != nil { fmt.Println("写入失败:", err) return } fmt.Println("创建成功") } -
使用场景示例
-
写入多个文件
- 示例:
tw := tar.NewWriter(file) defer tw.Close() // 文件 1 header1 := &tar.Header{ Name: "file1.txt", Mode: 0644, Size: 10, } tw.WriteHeader(header1) tw.Write([]byte("content1")) // 文件 2 header2 := &tar.Header{ Name: "file2.txt", Mode: 0644, Size: 10, } tw.WriteHeader(header2) tw.Write([]byte("content2"))
- 示例:
-
写入目录结构
- 示例:
tw := tar.NewWriter(file) defer tw.Close() // 先写目录 dirHeader := &tar.Header{ Name: "mydir/", Mode: 0755, Typeflag: tar.TypeDir, } tw.WriteHeader(dirHeader) // 再写目录中的文件 fileHeader := &tar.Header{ Name: "mydir/file.txt", Mode: 0644, Size: 10, } tw.WriteHeader(fileHeader) tw.Write([]byte("content"))
- 示例:
-
写入符号链接
- 示例:
tw := tar.NewWriter(file) defer tw.Close() linkHeader := &tar.Header{ Name: "link.txt", Linkname: "target.txt", Typeflag: tar.TypeSymlink, } tw.WriteHeader(linkHeader)
- 示例:
-
从实际文件创建
- 示例:
fileInfo, _ := os.Stat("source.txt") header, _ := tar.FileInfoHeader(fileInfo, "") header.Name = "archive/source.txt" tw.WriteHeader(header) sourceFile, _ := os.Open("source.txt") defer sourceFile.Close() io.Copy(tw, sourceFile)
- 示例:
-
🔹 函数
创建 tar 读取器
tar.NewReader(r io.Reader) *tar.Reader
-
说明:
- 从给定的 io.Reader 创建一个新的 tar 读取器
- 返回的 tar.Reader 可以用于读取 tar 归档内容
-
参数:
- r io.Reader - 任何实现了 io.Reader 接口的对象(如 *os.File、*bytes.Buffer 等)
-
返回值:
- *tar.Reader - tar 读取器指针
-
注意事项:
- 不会验证输入数据是否是有效的 tar 格式
- 实际的格式验证在调用 Next() 时进行
- 通常与 gzip.NewReader 等结合使用处理压缩文件
-
示例(完整)
package main import ( "archive/tar" "fmt" "io" "os" ) func main() { // 打开 tar 文件 file, err := os.Open("archive.tar") if err != nil { fmt.Println("打开失败:", err) return } defer file.Close() // 创建读取器 tr := tar.NewReader(file) // 遍历归档 for { header, err := tr.Next() if err == io.EOF { break } if err != nil { fmt.Println("读取失败:", err) return } fmt.Printf("文件:%s\n", header.Name) } } -
使用场景示例
-
从 bytes.Buffer 创建
- 示例:
var buf bytes.Buffer // ... buf 中已有 tar 数据 tr := tar.NewReader(&buf)
- 示例:
-
读取 gzip 压缩的 tar
- 示例:
file, _ := os.Open("archive.tar.gz") gr, _ := gzip.NewReader(file) tr := tar.NewReader(gr)
- 示例:
-
从 HTTP 响应创建
- 示例:
resp, _ := http.Get("http://example.com/archive.tar") defer resp.Body.Close() tr := tar.NewReader(resp.Body)
- 示例:
-
创建 tar 写入器
tar.NewWriter(w io.Writer) *tar.Writer
-
说明:
- 创建一个向给定 io.Writer 写入的 tar 写入器
- 返回的 tar.Writer 可以用于创建 tar 归档
-
参数:
- w io.Writer - 任何实现了 io.Writer 接口的对象(如 *os.File、*bytes.Buffer 等)
-
返回值:
- *tar.Writer - tar 写入器指针
-
注意事项:
- 使用完成后必须调用 Close() 方法
- Close() 会写入结束标记
- 应该在 defer 中调用 Close() 确保资源释放
-
示例(完整)
package main import ( "archive/tar" "bytes" "fmt" ) func main() { // 使用 bytes.Buffer 作为目标 var buf bytes.Buffer // 创建写入器 tw := tar.NewWriter(&buf) defer tw.Close() // 写入文件头 header := &tar.Header{ Name: "test.txt", Mode: 0644, Size: 11, } tw.WriteHeader(header) tw.Write([]byte("hello world")) fmt.Printf("tar 归档创建成功,缓冲区大小:%d\n", buf.Len()) } -
使用场景示例
-
写入到文件
- 示例:
file, _ := os.Create("archive.tar") defer file.Close() tw := tar.NewWriter(file) defer tw.Close()
- 示例:
-
写入到内存
- 示例:
var buf bytes.Buffer tw := tar.NewWriter(&buf) defer tw.Close() // 写入完成后 buf.Bytes() 包含 tar 数据
- 示例:
-
创建 gzip 压缩的 tar
- 示例:
file, _ := os.Create("archive.tar.gz") defer file.Close() gw := gzip.NewWriter(file) defer gw.Close() tw := tar.NewWriter(gw) defer tw.Close()
- 示例:
-
🔹 tar.Header 方法
写入文件头
(*tar.Writer).WriteHeader(hdr *Header) error
-
示例
package main import ( "archive/tar" "fmt" "os" ) func main() { file, err := os.Create("test.tar") if err != nil { fmt.Println("创建失败:", err) return } defer file.Close() tw := tar.NewWriter(file) defer tw.Close() // 写入普通文件 header := &tar.Header{ Name: "file.txt", Mode: 0644, Size: 13, } if err := tw.WriteHeader(header); err != nil { fmt.Println("写入头失败:", err) return } // 写入目录 dirHeader := &tar.Header{ Name: "mydir/", Mode: 0755, Typeflag: tar.TypeDir, } if err := tw.WriteHeader(dirHeader); err != nil { fmt.Println("写入目录头失败:", err) return } fmt.Println("写入成功") }
写入文件内容
(*tar.Writer).Write(b []byte) (int, error)
-
示例
package main import ( "archive/tar" "fmt" "os" "strings" ) func main() { file, err := os.Create("content.tar") if err != nil { fmt.Println("创建失败:", err) return } defer file.Close() tw := tar.NewWriter(file) defer tw.Close() content := "Hello, World!" // 写入文件头 header := &tar.Header{ Name: "hello.txt", Mode: 0644, Size: int64(len(content)), } if err := tw.WriteHeader(header); err != nil { fmt.Println("写入头失败:", err) return } // 写入内容 n, err := tw.Write([]byte(content)) if err != nil { fmt.Println("写入失败:", err) return } fmt.Printf("写入了 %d 字节\n", n) }
关闭写入器
(*tar.Writer).Close() error
-
说明:完成 tar 归档写入,必须调用
-
示例
package main import ( "archive/tar" "fmt" "os" ) func main() { file, err := os.Create("final.tar") if err != nil { fmt.Println("创建失败:", err) return } tw := tar.NewWriter(file) // 写入一些文件 header := &tar.Header{ Name: "test.txt", Mode: 0644, Size: 5, } tw.WriteHeader(header) tw.Write([]byte("hello")) // 关闭写入器 if err := tw.Close(); err != nil { fmt.Println("关闭失败:", err) return } // 关闭文件 file.Close() fmt.Println("tar 归档创建完成") }
读取下一个文件头
(*tar.Reader).Next() (*Header, error)
-
示例
package main import ( "archive/tar" "fmt" "io" "os" ) func main() { file, err := os.Open("archive.tar") if err != nil { fmt.Println("打开失败:", err) return } defer file.Close() tr := tar.NewReader(file) // 遍历所有文件 for { header, err := tr.Next() if err == io.EOF { break } if err != nil { fmt.Println("读取失败:", err) return } fmt.Printf("文件:%s\n", header.Name) fmt.Printf(" 类型:%c\n", header.Typeflag) fmt.Printf(" 大小:%d 字节\n", header.Size) fmt.Printf(" 权限:%o\n", header.Mode) } }
读取文件内容
(*tar.Reader).Read(b []byte) (int, error)
-
示例
package main import ( "archive/tar" "fmt" "io" "os" ) func main() { file, err := os.Open("archive.tar") if err != nil { fmt.Println("打开失败:", err) return } defer file.Close() tr := tar.NewReader(file) for { header, err := tr.Next() if err == io.EOF { break } if err != nil { fmt.Println("读取失败:", err) return } // 跳过目录 if header.Typeflag == tar.TypeDir { continue } // 读取文件内容 content, err := io.ReadAll(tr) if err != nil { fmt.Println("读取内容失败:", err) return } fmt.Printf("%s:\n%s\n\n", header.Name, string(content)) } }
🔹 实际应用示例
创建 tar 归档
-
示例
package main import ( "archive/tar" "fmt" "io/fs" "os" "path/filepath" ) func createTar(source, target string) error { // 创建 tar 文件 tarFile, err := os.Create(target) if err != nil { return err } defer tarFile.Close() tw := tar.NewWriter(tarFile) defer tw.Close() // 遍历源目录 return filepath.Walk(source, func(path string, info fs.FileInfo, err error) error { if err != nil { return err } // 获取相对路径 relPath, err := filepath.Rel(source, path) if err != nil { return err } // 跳过根目录 if relPath == "." { return nil } // 创建文件头 header, err := tar.FileInfoHeader(info, "") if err != nil { return err } // 设置相对路径 header.Name = filepath.ToSlash(relPath) // 写入文件头 if err := tw.WriteHeader(header); err != nil { return err } // 如果是目录,直接返回 if info.IsDir() { return nil } // 读取并写入文件内容 file, err := os.Open(path) if err != nil { return err } defer file.Close() _, err = io.Copy(tw, file) return err }) } func main() { err := createTar("./source", "./backup.tar") if err != nil { fmt.Println("创建失败:", err) return } fmt.Println("tar 归档创建成功") }
解压 tar 归档
-
示例
package main import ( "archive/tar" "fmt" "io" "os" "path/filepath" "strings" ) func extractTar(tarFile, dest string) error { // 打开 tar 文件 file, err := os.Open(tarFile) if err != nil { return err } defer file.Close() tr := tar.NewReader(file) for { header, err := tr.Next() if err == io.EOF { break } if err != nil { return err } // 构建目标路径 target := filepath.Join(dest, header.Name) // 安全检查:防止路径遍历攻击 if !strings.HasPrefix(target, dest) { fmt.Printf("跳过不安全路径:%s\n", target) continue } // 根据类型处理 switch header.Typeflag { case tar.TypeDir: // 创建目录 if err := os.MkdirAll(target, os.FileMode(header.Mode)); err != nil { return err } case tar.TypeReg: // 创建父目录 if err := os.MkdirAll(filepath.Dir(target), 0755); err != nil { return err } // 创建文件 outFile, err := os.Create(target) if err != nil { return err } // 复制内容 if _, err := io.Copy(outFile, tr); err != nil { outFile.Close() return err } outFile.Close() // 设置权限 os.Chmod(target, os.FileMode(header.Mode)) case tar.TypeSymlink: // 创建符号链接 os.Symlink(header.Linkname, target) default: fmt.Printf("跳过未知类型:%c\n", header.Typeflag) } } return nil } func main() { err := extractTar("./backup.tar", "./restored") if err != nil { fmt.Println("解压失败:", err) return } fmt.Println("解压成功") }
向 tar 添加文件
-
示例
package main import ( "archive/tar" "fmt" "io" "os" "path/filepath" ) func addFileToTar(tarPath, filePath, archivePath string) error { // 打开现有 tar 文件(或创建新的) tarFile, err := os.OpenFile(tarPath, os.O_RDWR|os.O_CREATE, 0644) if err != nil { return err } defer tarFile.Close() // 读取现有内容到内存 var existingData []byte tr := tar.NewReader(tarFile) for { header, err := tr.Next() if err == io.EOF { break } if err != nil { return err } // 保存文件头 existingData = append(existingData, []byte(header.Name)...) } // 重新创建 tar 文件 tarFile.Close() tarFile, err = os.Create(tarPath) if err != nil { return err } defer tarFile.Close() tw := tar.NewWriter(tarFile) defer tw.Close() // 打开要添加的文件 file, err := os.Open(filePath) if err != nil { return err } defer file.Close() // 获取文件信息 info, err := file.Stat() if err != nil { return err } // 创建文件头 header := &tar.Header{ Name: archivePath, Mode: 0644, Size: info.Size(), } // 写入文件头 if err := tw.WriteHeader(header); err != nil { return err } // 写入内容 _, err = io.Copy(tw, file) return err } func main() { err := addFileToTar("./archive.tar", "./newfile.txt", "docs/newfile.txt") if err != nil { fmt.Println("添加失败:", err) return } fmt.Println("文件添加成功") }
从 tar 读取特定文件
-
示例
package main import ( "archive/tar" "fmt" "io" "os" "strings" ) func readFileFromTar(tarPath, fileName string) ([]byte, error) { file, err := os.Open(tarPath) if err != nil { return nil, err } defer file.Close() tr := tar.NewReader(file) for { header, err := tr.Next() if err == io.EOF { break } if err != nil { return nil, err } // 查找目标文件 if header.Name == fileName || strings.HasSuffix(header.Name, fileName) { content, err := io.ReadAll(tr) if err != nil { return nil, err } return content, nil } } return nil, fmt.Errorf("文件未找到:%s", fileName) } func main() { content, err := readFileFromTar("./archive.tar", "config.txt") if err != nil { fmt.Println("读取失败:", err) return } fmt.Printf("文件内容:\n%s\n", string(content)) }
列出 tar 归档内容
-
示例
package main import ( "archive/tar" "fmt" "io" "os" "time" ) func listTarContents(tarPath string) error { file, err := os.Open(tarPath) if err != nil { return err } defer file.Close() tr := tar.NewReader(file) fmt.Printf("%-40s %-8s %-10s %s\n", "文件名", "类型", "大小", "修改时间") fmt.Println(strings.Repeat("-", 80)) for { header, err := tr.Next() if err == io.EOF { break } if err != nil { return err } // 确定类型 fileType := "文件" switch header.Typeflag { case tar.TypeDir: fileType = "目录" case tar.TypeSymlink: fileType = "链接" case tar.TypeChar: fileType = "字符设备" case tar.TypeBlock: fileType = "块设备" case tar.TypeFifo: fileType = "FIFO" } // 格式化大小 size := fmt.Sprintf("%d", header.Size) if header.Typeflag == tar.TypeDir { size = "-" } // 格式化时间 timeStr := header.ModTime.Format("2006-01-02 15:04") fmt.Printf("%-40s %-8s %-10s %s\n", header.Name, fileType, size, timeStr) } return nil } func main() { err := listTarContents("./backup.tar") if err != nil { fmt.Println("列出失败:", err) return } }
压缩和解压完整示例
-
示例
package main import ( "archive/tar" "compress/gzip" "fmt" "io" "io/fs" "os" "path/filepath" "strings" ) // 创建 tar.gz 归档 func createTarGz(source, target string) error { // 创建文件 file, err := os.Create(target) if err != nil { return err } defer file.Close() // 创建 gzip 写入器 gw := gzip.NewWriter(file) defer gw.Close() // 创建 tar 写入器 tw := tar.NewWriter(gw) defer tw.Close() // 遍历源目录 return filepath.Walk(source, func(path string, info fs.FileInfo, err error) error { if err != nil { return err } // 获取相对路径 relPath, err := filepath.Rel(source, path) if err != nil { return err } if relPath == "." { return nil } // 创建文件头 header, err := tar.FileInfoHeader(info, "") if err != nil { return err } header.Name = filepath.ToSlash(relPath) // 写入文件头 if err := tw.WriteHeader(header); err != nil { return err } // 跳过目录 if info.IsDir() { return nil } // 写入内容 file, err := os.Open(path) if err != nil { return err } defer file.Close() _, err = io.Copy(tw, file) return err }) } // 解压 tar.gz 归档 func extractTarGz(tarGzFile, dest string) error { // 打开文件 file, err := os.Open(tarGzFile) if err != nil { return err } defer file.Close() // 创建 gzip 读取器 gr, err := gzip.NewReader(file) if err != nil { return err } defer gr.Close() // 创建 tar 读取器 tr := tar.NewReader(gr) for { header, err := tr.Next() if err == io.EOF { break } if err != nil { return err } target := filepath.Join(dest, header.Name) // 安全检查 if !strings.HasPrefix(target, dest) { continue } switch header.Typeflag { case tar.TypeDir: os.MkdirAll(target, os.FileMode(header.Mode)) case tar.TypeReg: os.MkdirAll(filepath.Dir(target), 0755) outFile, err := os.Create(target) if err != nil { return err } io.Copy(outFile, tr) outFile.Close() os.Chmod(target, os.FileMode(header.Mode)) } } return nil } func main() { // 创建压缩归档 fmt.Println("创建 tar.gz 归档...") err := createTarGz("./source", "./backup.tar.gz") if err != nil { fmt.Println("创建失败:", err) return } fmt.Println("创建成功") // 解压归档 fmt.Println("\n解压 tar.gz 归档...") err = extractTarGz("./backup.tar.gz", "./restored") if err != nil { fmt.Println("解压失败:", err) return } fmt.Println("解压成功") }
� 错误处理
常见错误
-
io.EOF
- 说明:读取到归档末尾
- 处理方式:正常结束读取循环
- 示例:
for { header, err := tr.Next() if err == io.EOF { break // 正常结束 } if err != nil { return err } // 处理文件... }
-
tar.ErrHeader
- 说明:文件头格式错误
- 原因:数据损坏或不是有效的 tar 格式
- 示例:
_, err := tr.Next() if err == tar.ErrHeader { fmt.Println("无效的文件头") }
-
写入大小不匹配
- 说明:写入的数据量与 Header.Size 不匹配
- 原因:Write 写入的字节数不等于 Header.Size
- 示例:
header := &tar.Header{ Name: "file.txt", Size: 10, } tw.WriteHeader(header) tw.Write([]byte("short")) // 只有 5 字节,会导致错误
错误处理最佳实践
-
读取时的错误处理
- 示例:
for { header, err := tr.Next() if err == io.EOF { break } if err != nil { fmt.Fprintf(os.Stderr, "读取失败:%v\n", err) return err } _, err = io.Copy(dst, tr) if err != nil { fmt.Fprintf(os.Stderr, "复制失败:%v\n", err) return err } }
- 示例:
-
写入时的错误处理
- 示例:
tw := tar.NewWriter(file) defer tw.Close() if err := tw.WriteHeader(header); err != nil { fmt.Fprintf(os.Stderr, "写入头失败:%v\n", err) return err } if _, err := tw.Write(content); err != nil { fmt.Fprintf(os.Stderr, "写入内容失败:%v\n", err) return err }
- 示例:
-
资源清理
- 示例:
file, err := os.Open("archive.tar") if err != nil { return err } defer file.Close() // 确保文件关闭 tr := tar.NewReader(file) // 使用读取器...
- 示例:
�🔥 总结
核心类型
- tar.Header 👉 文件头信息
- tar.Reader 👉 读取 tar 归档
- tar.Writer 👉 写入 tar 归档
常用函数
- tar.NewReader() 👉 创建读取器
- tar.NewWriter() 👉 创建写入器
文件类型常量
- tar.TypeReg 👉 普通文件 (‘0’)
- tar.TypeDir 👉 目录 (‘5’)
- tar.TypeSymlink 👉 符号链接 (‘2’)
- tar.TypeChar 👉 字符设备 (‘1’)
- tar.TypeBlock 👉 块设备 (‘3’)
- tar.TypeFifo 👉 FIFO 管道 (‘6’)
关键方法
- WriteHeader() 👉 写入文件头
- Write() 👉 写入文件内容
- Next() 👉 读取下一个文件头
- Read() 👉 读取文件内容
- Close() 👉 关闭写入器
实际应用
- 备份和归档
- 文件打包传输
- 容器镜像层(Docker 使用 tar)
- 日志归档
- 配置文件打包