Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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)
  • 日志归档
  • 配置文件打包