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
}