Go标准库之tar

tar包实现了tar格式压缩文件的存取。本包目标是覆盖大多数tar的变种,包括GNU和BSD生成的tar文件。

// Constants
const (
  // 类型
  TypeReg           = '0'    // 普通文件
  TypeRegA          = '\x00' // 普通文件
  TypeLink          = '1'    // 硬链接
  TypeSymlink       = '2'    // 符号链接
  TypeChar          = '3'    // 字符设备节点
  TypeBlock         = '4'    // 块设备节点
  TypeDir           = '5'    // 目录
  TypeFifo          = '6'    // 先进先出队列节点
  TypeCont          = '7'    // 保留位
  TypeXHeader       = 'x'    // 扩展头
  TypeXGlobalHeader = 'g'    // 全局扩展头
  TypeGNULongName   = 'L'    // 下一个文件记录有个长名字
  TypeGNULongLink   = 'K'    // 下一个文件记录指向一个具有长名字的文件
  TypeGNUSparse     = 'S'    // 稀疏文件
)
// Struct
    // Header
    // Header代表 tar 档案文件里的单个头。Header类型的某些字段可能未使用。表示tar存档中的单个头。
type Header struct {
	Typeflag   byte //记录头的类型
	Name       string // 记录头域的文件名
	Linkname   string // 链接的目标名
	Size       int64  // 字节数(长度)
	Mode       int64     // 权限和模式位
	Uid        int       // 所有者的用户ID
	Gid        int       // 所有者的组ID
	ModTime    time.Time // 修改时间
	Uname      string    // 所有者的用户名
	Gname      string    // 所有者的组名
	AccessTime time.Time // 访问时间
	ChangeTime time.Time // 状态改变时间
	Devmajor   int64     // 字符设备或块设备的major number
	Devminor   int64     // 字符设备或块设备的minor number
	Xattrs     map[string]string
	PAXRecords map[string]string
	Format     Format    //格式指定tar头的格式。这是由读者设置的。如果在调用writer.writeHeader时未指定格式,然后使用第一种格式(按照USTAR、PAX、GNU的顺序)能够对此头进行编码(Format)。
}
        // Function:
            func (h *Header) FileInfo() os.FileInfo 
            // FileInfo返回该 Header 对应的文件信息。( os.FileInfo 类型)

    // Reader
    // Reader提供了对一个 tar 档案文件的顺序读取。一个 tar 档案文件包含一系列文件。 Next 方法返回档案中的下一个文件(包括第一个),返回值可以被视为 io.Reader 来获取文件的数据。
type Reader struct {
	r    io.Reader
	pad  int64      // 当前文件项后的填充量(忽略)
	curr fileReader // 当前文件项的读取器
	blk  block      // 用作临时本地存储的缓冲区
	err  error
}
        // Function
            func (tr *Reader) Next() (*Header, error)
            // 转入 tar 档案文件下一记录,它会返回下一记录的头域。
            func (tr *Reader) Read(b []byte) (n int, err error)
            // 从档案文件的当前记录读取数据,到达记录末端时返回(0,EOF),直到调用 Next 方法转入下一记录。


     // Writer
     // Writer类型提供了POSIX.1格式的 tar 档案文件的顺序写入。一个 tar 档案文件包含一系列文件。调用 WriteHeader 来写入一个新的文件,然后调用 Write 写入文件的数据,该记录写入的数据不能超过hdr.Size字节。
type Writer struct {
	w    io.Writer
	pad  int64      //当前文件项后要写入的填充量
	curr fileWriter // 当前文件的写入器
	hdr  Header     // 对突变安全的标题的浅拷贝
	blk  block      // 用作临时本地存储的缓冲区
	err  error
}
        // Function
            func (tw *Writer) Close() error
            // Close关闭 tar 档案文件,会将缓冲中未写入下层的 io.Writer 接口的数据刷新到下层。
            func (tw *Writer) Flush() error
            // Flush结束当前文件的写入。(可选的)
            func (tw *Writer) Write(b []byte) (n int, err error)
            // Write向 tar 档案文件的当前记录中写入数据。如果写入的数据总数超出上一次调用 WriteHeader 的参数hdr.Size字节,返回ErrWriteTooLong错误。
            func (tw *Writer) WriteHeader(hdr *Header) error
            // WriteHeader写入hdr并准备接受文件内容。如果不是第一次调用本方法,会调用 Flush 。在 Close 之后调用本方法会返回ErrWriteAfterClose。


// Function
    func FileInfoHeader(fi os.FileInfo, link string) (*Header, error)
    // FileInfoHeader返回一个根据fi填写了部分字段的Header。如果fi描述一个符号链接,FileInfoHeader函数将link参数作为链接目标。如果fi描述一个目录,会在名字后面添加斜杠。因为 os.FileInfo 接口的Name方法只返回它描述的文件的无路径名,有可能需要将返回值的Name字段修改为文件的完整路径名。

    func NewReader(r io.Reader) *Reader
    // NewReader创建一个从r读取的Reader。

    func NewWriter(w io.Writer) *Writer
    // NewWriter创建一个写入w的*Writer。

Demo

// Header信息读取
func headerDemo() {
	fileName := "./tarFile.tar"
	sfileInfo, err := os.Stat(fileName)
	if err != nil {
		fmt.Println("get file status Err,", err)
		return
	}
	header, err := tar.FileInfoHeader(sfileInfo, "")
	if err != nil {
		fmt.Println("get Header Info Err,", err)
		return
	}
	fmt.Println(header.Name)
	fmt.Println(header.Mode)
	fmt.Println(header.Uid)
	fmt.Println(header.Gid)
	fmt.Println(header.Size)
	fmt.Println(header.ModTime)
	fmt.Println(header.Typeflag)
	fmt.Println(header.Linkname)
	fmt.Println(header.Uname)
	fmt.Println(header.Gname)
	fmt.Println(header.Devmajor)
	fmt.Println(header.Devminor)
	fmt.Println(header.AccessTime)
	fmt.Println(header.ChangeTime)
	fmt.Println(header.Xattrs)
}


// 打包函数例子

func tarDemo() {
	filetarget := "./tarFile.tar"
	filesource := "./filedata"
	tarfile, err := os.Create(filetarget)
	if err != nil {
		// if file is exist then delete file
		if err == os.ErrExist {
			if err := os.Remove(filetarget); err != nil {
				fmt.Println(err)
			}
		} else {
			fmt.Println(err)
		}
	}
	defer tarfile.Close()
	tarwriter := tar.NewWriter(tarfile)
	sfileInfo, err := os.Stat(filesource)
	if err != nil {
		fmt.Println(err)
	}
	if !sfileInfo.IsDir() {
		tarFile(filesource, sfileInfo, tarwriter)
	} else {
		tarFolder(filesource, tarwriter)
	}
}


// tar 单个文件
//压缩单个文件
func tarFile(filesource string, sfileInfo os.FileInfo, tarwriter *tar.Writer) error {
	sfile, err := os.Open(filesource)
	if err != nil {
		fmt.Println(err)
		return err
	}
	defer sfile.Close()
	header, err := tar.FileInfoHeader(sfileInfo, "")
	if err != nil {
		fmt.Println(err)
		return err
	}
	header.Name = filesource
	err = tarwriter.WriteHeader(header)
	if err != nil {
		fmt.Println(err)
		return err
	}
	if _, err = io.Copy(tarwriter, sfile); err != nil {
		fmt.Println(err)
		return err
	}
	return nil
}

// tar 一个目录
//压缩文件夹
func tarFolder(directory string, tarwriter *tar.Writer) error {
	return filepath.Walk(directory, func(targetpath string, file os.FileInfo, err error) error {
		//read the file failure
		if file == nil {
			return err
		}
		if file.IsDir() {
			if directory == targetpath {
				return nil
			}
			header, err := tar.FileInfoHeader(file, "")
			if err != nil {
				return err
			}
			header.Name = filepath.Join(directory, strings.TrimPrefix(targetpath, directory))
			if err = tarwriter.WriteHeader(header); err != nil {
				return err
			}
			os.Mkdir(strings.TrimPrefix(directory, file.Name()), os.ModeDir)
			//如果压缩的目录里面还有目录,则递归压缩
			return tarFolder(targetpath, tarwriter)
		}
		return tarFile(targetpath, file, tarwriter)
	})
}

解压

func untarFile(tarFile string, untarPath string) error {
	//打开要解包的文件,tarFile是要解包的 .tar 文件的路径
	fr, er := os.Open(tarFile)
	if er != nil {
		return er
	}
	defer fr.Close()
	// 创建 tar.Reader,准备执行解包操作
	tr := tar.NewReader(fr)
	//用 tr.Next() 来遍历包中的文件,然后将文件的数据保存到磁盘中
	for hdr, er := tr.Next(); er != io.EOF; hdr, er = tr.Next() {
		if er != nil {
			return er
		}
		//先创建目录
		fileName := untarPath + "/" + hdr.Name
		dir := path.Dir(fileName)
		_, err := os.Stat(dir)
		//如果err 为空说明文件夹已经存在,就不用创建
		if err != nil {
			err = os.MkdirAll(dir, os.ModePerm)
			if err != nil {
				fmt.Print(err)
				return err
			}
		}
		//获取文件信息
		fi := hdr.FileInfo()
		//创建空文件,准备写入解压后的数据
		fw, er := os.Create(fileName)
		if er != nil {
			return er
		}
		defer fw.Close()
		// 写入解压后的数据
		_, er = io.Copy(fw, tr)
		if er != nil {
			return er
		}
		// 设置文件权限
		os.Chmod(fileName, fi.Mode().Perm())
	}
	return nil
}

posted @ 2020-03-26 13:05  Binb  阅读(844)  评论(0编辑  收藏  举报