当前位置: 首页 > article >正文

golang标准库archive/tar实现打包压缩及解压


文章目录

  • 前言
  • 一、单个文件操作
    • 1.单个文件打包示例
    • 2.单个文件解包示例
  • 二、目录示例
    • 1.打包压缩
    • 2.解包
  • 补充


前言

这个包就是将文件进行打包和解包,通俗理解就是Linux 下的 tar 命令。 主要是通过 tar.Reader 读取 tar 包,通过 tar.Writer 写入 tar包,在写入的过程中再设置一下头,详细的过程以示例的方式进行展示,可以查看代码里面的注释。

标准库 tar 中文文档https://studygolang.com/static/pkgdoc/pkg/archive_tar.html


一、单个文件操作

1.单个文件打包示例

package main

import (
    "os"
    "log"
    "archive/tar"
    "fmt"
    "io"
)

func main() {
    // 准备打包的源文件
    var srcFile = "sshd"
    // 打包后的文件
    var desFile = fmt.Sprintf("%s.tar",srcFile)

    // 需要注意文件的打开即关闭的顺序,因为 defer 是后入先出,所以关闭顺序很重要
    // 第一次写这个示例的时候就没注意,导致写完的 tar 包不完整

    // ###### 第 1 步,先准备好一个 tar.Writer 结构,然后再向里面写入内容。 ######
    // 创建一个文件,用来保存打包后的 passwd.tar 文件
    fw, err := os.Create(desFile)
    ErrPrintln(err)
    defer fw.Close()

    // 通过 fw 创建一个 tar.Writer
    tw := tar.NewWriter(fw)
    // 这里不要忘记关闭,如果不能成功关闭会造成 tar 包不完整
    // 所以这里在关闭的同时进行判断,可以清楚的知道是否成功关闭
    defer func() {
        if err := tw.Close(); err != nil {
            ErrPrintln(err)
        }
    }()

    // ###### 第 2 步,处理文件信息,也就是 tar.Header 相关的 ######
    // tar 包共有两部分内容:文件信息和文件数据
    // 通过 Stat 获取 FileInfo,然后通过 FileInfoHeader 得到 hdr tar.*Header
    fi, err := os.Stat(srcFile)
    ErrPrintln(err)
    hdr, err := tar.FileInfoHeader(fi, "")
    // 将 tar 的文件信息 hdr 写入到 tw
    err = tw.WriteHeader(hdr)
    ErrPrintln(err)

    // 将文件数据写入
    // 打开准备写入的文件
    fr, err := os.Open(srcFile)
    ErrPrintln(err)
    defer fr.Close()

    written, err := io.Copy(tw, fr)
    ErrPrintln(err)

    log.Printf("共写入了 %d 个字符的数据\n",written)
}

// 定义一个用来打印的函数,少写点代码,因为要处理很多次的 err
// 后面其他示例还会继续使用这个函数,就不单独再写,望看到此函数了解
func ErrPrintln(err error)  {
    if err != nil {
        log.Println(err)
        os.Exit(1)
    }
}

2.单个文件解包示例

package main

import (
    "os"
    "archive/tar"
    "io"
    "log"
)

func main() {

    var srcFile = "passwd.tar"

    // 将 tar 包打开
    fr, err := os.Open(srcFile)
    ErrPrintln(err)
    defer fr.Close()

    // 通过 fr 创建一个 tar.*Reader 结构,然后将 tr 遍历,并将数据保存到磁盘中
    tr := tar.NewReader(fr)

    for hdr, err := tr.Next(); err != io.EOF; hdr, err = tr.Next(){
        // 处理 err != nil 的情况
        ErrPrintln(err)
        // 获取文件信息
        fi := hdr.FileInfo()

        // 创建一个空文件,用来写入解包后的数据
        fw, err := os.Create(fi.Name())
        ErrPrintln(err)

        // 将 tr 写入到 fw
        n, err := io.Copy(fw, tr)
        ErrPrintln(err)
        log.Printf("解包: %s 到 %s ,共处理了 %d 个字符的数据。", srcFile,fi.Name(),n)

        // 设置文件权限,这样可以保证和原始文件权限相同,如果不设置,会根据当前系统的 umask 来设置。
        os.Chmod(fi.Name(),fi.Mode().Perm())

        // 注意,因为是在循环中,所以就没有使用 defer 关闭文件
        // 如果想使用 defer 的话,可以将文件写入的步骤单独封装在一个函数中即可
        fw.Close()
    }
}

func ErrPrintln(err error){
    if err != nil {
        log.Fatalln(err)
        os.Exit(1)
    }
}

二、目录示例

打包整个目录,且打包的时候通过 gzip 或者 bzip2 压缩。如果要打包整个目录,可以通过递归的方式来实现。此处只演示 gzip 方式压缩,这个实现非常简单,只需要在 fw 和 tw 之前加上一层压缩即可

1.打包压缩

代码如下(示例):

package main

import (
    "archive/tar"
    "compress/gzip"
    "fmt"
    "io"
    "os"
    "path/filepath"
)
func createTarFile(tarFilePath string, sourceDir string) error {
	// 创建Tar文件
	tarFile, err := os.Create(tarFilePath)
	if err != nil {
		return err
	}
	defer tarFile.Close()
	
	// 将 tar 包使用 gzip 压缩,其实添加压缩功能很简单,
    // 只需要在 fw 和 tw 之前加上一层压缩就行了,和 Linux 的管道的感觉类似
    gw := gzip.NewWriter(tarFile)
    defer gw.Close()

	// 创建Tar写入器
	tarWriter := tar.NewWriter(tarFile)
	defer tarWriter.Close()

	// 遍历源目录并添加文件到Tar
	err = filepath.Walk(sourceDir, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		// 创建Tar头信息
		header, err := tar.FileInfoHeader(info, "")
		if err != nil {
			return err
		}

		// 设置文件路径
		//这里的思路就是递归处理目录及目录下的所有文件和目录
		header.Name, err = filepath.Rel(filepath.Dir(sourceDir), path)
		fmt.Printf("header.Name:%s\n", header.Name)
		if err != nil {
			return err
		}

		// 写入头信息
		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()
			_, err = io.Copy(tarWriter, file)
			return err
		}

		return nil
	})

	return err
}
func main(){
	tarFilePath := "output.tar"
    sourceDir := "/etc/sshd/sshd_config"
    err = createTarFile(tarFilePath, sourceDir)
	if err != nil {
		panic(err)
	}
}

2.解包

代码如下(示例):

package main

import (
    "archive/tar"
    "compress/gzip"
    "fmt"
    "io"
    "os"
    "path/filepath"
)

func main() {
    var dst = "" // 不写就是解压到当前目录
    var src = "log.tar.gz"

    UnTar(dst, src)
}

func UnTar(dst, src string) (err error) {
    // 打开准备解压的 tar 包
    fr, err := os.Open(src)
    if err != nil {
        return
    }
    defer fr.Close()

    // 将打开的文件先解压
    gr, err := gzip.NewReader(fr)
    if err != nil {
        return
    }
    defer gr.Close()

    // 通过 gr 创建 tar.Reader
    tr := tar.NewReader(gr)

    // 现在已经获得了 tar.Reader 结构了,只需要循环里面的数据写入文件就可以了
    for {
        hdr, err := tr.Next()

        switch {
        case err == io.EOF:
            return nil
        case err != nil:
            return err
        case hdr == nil:
            continue
        }

        // 处理下保存路径,将要保存的目录加上 header 中的 Name
        // 这个变量保存的有可能是目录,有可能是文件,所以就叫 FileDir 了……
        dstFileDir := filepath.Join(dst, hdr.Name)

        // 根据 header 的 Typeflag 字段,判断文件的类型
        switch hdr.Typeflag {
        case tar.TypeDir: // 如果是目录时候,创建目录
            // 判断下目录是否存在,不存在就创建
            if b := ExistDir(dstFileDir); !b {
                // 使用 MkdirAll 不使用 Mkdir ,就类似 Linux 终端下的 mkdir -p,
                // 可以递归创建每一级目录
                if err := os.MkdirAll(dstFileDir, 0775); err != nil {
                    return err
                }
            }
        case tar.TypeReg: // 如果是文件就写入到磁盘
            // 创建一个可以读写的文件,权限就使用 header 中记录的权限
            // 因为操作系统的 FileMode 是 int32 类型的,hdr 中的是 int64,所以转换下
            file, err := os.OpenFile(dstFileDir, os.O_CREATE|os.O_RDWR, os.FileMode(hdr.Mode))
            if err != nil {
                return err
            }
            n, err := io.Copy(file, tr)
            if err != nil {
                return err
            }
            // 将解压结果输出显示
            fmt.Printf("成功解压: %s , 共处理了 %d 个字符\n", dstFileDir, n)

            // 不要忘记关闭打开的文件,因为它是在 for 循环中,不能使用 defer
            // 如果想使用 defer 就放在一个单独的函数中
            file.Close()
        }
    }

    return nil
}

// 判断目录是否存在
func ExistDir(dirname string) bool {
    fi, err := os.Stat(dirname)
    return (err == nil || os.IsExist(err)) && fi.IsDir()
}

补充

1、archive/tar打包和解包的操作只能在当前服务器上执行,不能再远程服务器上操作,例如:你ssh到一台远程机器上执行这个打包操作,就会失败,实际上操作的还是当前机器
2、在使用 tar.Writer 时,需要使用 tar.Header 结构体设置文件的元信息,包括文件名、大小等。
3、在读取 tar 归档文件时,可以通过 tar.Reader 的 Next 方法获取下一个文件的头信息,并使用 io.Copy 复制文件内容


http://www.kler.cn/a/458808.html

相关文章:

  • python数据分析:使用pandas库读取和编辑Excel表
  • 试用ChatGPT的copilot编写一个程序从笔记本电脑获取语音输入和图像输入并调用开源大模型进行解析
  • Linux(Ubuntu)下ESP-IDF下载与安装完整流程(2)
  • 高等数学学习笔记 ☞ 函数的极限
  • ListenAI 1.0.6 | 解锁订阅的文本转语音工具,支持朗读文档和网页
  • 基于TCP的Qt网络通信
  • 《Java核心技术 卷II》流的创建
  • Vue el-data-picker选中开始时间,结束时间自动加半小时
  • 滑动窗口、流量控制和拥塞控制
  • C++笔记-对windows平台上lib和dll的进一步理解(2024-10-21
  • YOLOv8实战车辆目标检测
  • js混淆中 p[‘name‘] 来访问属性的好处
  • 若依前后端分离项目部署(使用docker)
  • C++ 设计模式:职责链模式(Chain of Responsibility)
  • MySQL:一文弄懂时区time_zone
  • 远程调用服务器jupter调试程序
  • word运行时错误‘-2147221164(80040154)’ 没有注册类的解决办法
  • C++ 设计模式:备忘录模式(Memento Pattern)
  • omi friend实战记录
  • Java重要面试名词整理(十六):SpringBoot
  • 基于springboot的美容院管理系统设计与实现(java项目源码+文档)
  • 代码随想录Day37 动态规划:完全背包理论基础,518.零钱兑换II,本周小结动态规划,377. 组合总和 Ⅳ,70. 爬楼梯(进阶版)。
  • React Native中的核心技术概念
  • 【GridView渐进全解】第一部分GridView基础
  • 前端参数formData格式
  • 扬声器阵列的波束成形相关的关键技术和国内外研究现状