Golang创建 .tar.gz 压缩包

  tar 包实现了文件的打包功能,可以将多个文件或目录存储到单一的 .tar 文件中,tar 本身不具有压缩功能,只能打包文件或目录:

import "archive/tar"

  这里以打包单个文件为例进行解说,后面会给出打包整个目录的详细示例。

  向 tar 文件中写入数据是通过 tar.Writer 完成的,所以首先要创建 tar.Writer,可以通过 tar.NewWriter 方法来创建它,该方法要求提供一个 os.Writer 对象,以便将打包后的数据写入该对象中。可以先创建一个文件,然后将该文件提供给 tar.NewWriter 使用。这样就可以将打包后的数据写入文件中:

// 创建空文件 fw 用于保存打包后的数据
// dstTar 是要创建的 .tar 文件的完整路径
fw, err := os.Create(dstTar)
if err != nil {
return err
}
defer fw.Close() // 通过 fw 创建 tar.Writer 对象
tw := tar.NewWriter(fw)
defer tw.Close()   此时,我们就拥有了一个 tar.Writer 对象 tw,可以用它来打包文件了。这里要注意一点,使用完 tw 后,一定要执行 tw.Close() 操作,因为 tar.Writer 使用了缓存,tw.Close() 会将缓存中的数据写入到文件中,同时 tw.Close() 还会向 .tar 文件的最后写入结束信息,如果不关闭 tw 而直接退出程序,那么将导致 .tar 文件不完整。   存储在 .tar 文件中的每个文件都由两部分组成:文件信息和文件内容,所以向 .tar 文件中写入每个文件都要分两步:第一步写入文件信息,第二步写入文件数据。对于目录来说,由于没有内容可写,所以只需要写入目录信息即可。   文件信息由 tar.Header 结构体定义: type Header struct {
Name string // 文件名称
Mode int64 // 文件的权限和模式位
Uid int // 文件所有者的用户 ID
Gid int // 文件所有者的组 ID
Size int64 // 文件的字节长度
ModTime time.Time // 文件的修改时间
Typeflag byte // 文件的类型
Linkname string // 链接文件的目标名称
Uname string // 文件所有者的用户名
Gname string // 文件所有者的组名
Devmajor int64 // 字符设备或块设备的主设备号
Devminor int64 // 字符设备或块设备的次设备号
AccessTime time.Time // 文件的访问时间
ChangeTime time.Time // 文件的状态更改时间
}   我们首先将被打包文件的信息填入 tar.Header 结构体中,然后再将结构体写入 .tar 文件中。这样就完成了第一步(写入文件信息)操作。   在 tar 包中有一个很方便的函数 tar.FileInfoHeader,它可以直接通过 os.FileInfo 创建 tar.Header,并自动填写 tar.Header 中的大部分信息,当然,还有一些信息无法从 os.FileInfo 中获取,所以需要你自己去补充: // 获取文件信息
// srcFile 是要打包的文件的完整路径
fi, err := os.Stat(srcFile)
if err != nil {
return err
} // 根据 os.FileInfo 创建 tar.Header 结构体
hdr, err := tar.FileInfoHeader(fi, "")
if err != nil {
return err
}   这里的 hdr 就是文件信息结构体,已经填写完毕。如果你要填写的更详细,你可以自己将 hdr 补充完整。   下面通过 tw.WriteHeader 方法将 hdr 写入 .tar 文件中(tw 是我们刚才创建的 tar.Writer): // 将 tar.Header 写入 .tar 文件中
err = tw.WriteHeader(hdr)
if err != nil {
return err
}   至此,第一步(写入文件信息)操作完毕,下面开始第二步(写入文件数据)操作,写入数据很简单,通过 tw.Write 方法写入数据即可: // 打开要打包的文件准备读取
fr, err := os.Open(srcFile)
if err != nil {
return err
}
defer fr.Close() // 将文件数据写入 .tar 文件中,这里通过 io.Copy 函数实现数据的写入
_, err = io.Copy(tw, fr)
if err != nil {
return err
}   下面说说解包的方法,从 .tar 文件中读出数据是通过 tar.Reader 完成的,所以首先要创建 tar.Reader,可以通过 tar.NewReader 方法来创建它,该方法要求提供一个 os.Reader 对象,以便从该对象中读出数据。可以先打开一个 .tar 文件,然后将该文件提供给 tar.NewReader 使用。这样就可以将 .tar 文件中的数据读出来了: // 打开要解包的文件,srcTar 是要解包的 .tar 文件的路径
fr, er := os.Open(srcTar)
if er != nil {
return er
}
defer fr.Close() // 创建 tar.Reader,准备执行解包操作
tr := tar.NewReader(fr)   此时,我们就拥有了一个 tar.Reader 对象 tr,可以用 tr.Next() 来遍历包中的文件,然后将文件的数据保存到磁盘中: // 遍历包中的文件
for hdr, er := tr.Next(); er != io.EOF; hdr, er = tr.Next() {
if er != nil {
return er
} // 获取文件信息
fi := hdr.FileInfo() // 创建空文件,准备写入解压后的数据
fw, _ := os.Create(dstFullPath)
if er != nil {
return er
}
defer fw.Close() // 写入解压后的数据
_, er = io.Copy(fw, tr)
if er != nil {
return er
}
// 设置文件权限
os.Chmod(dstFullPath, fi.Mode().Perm())
}   至此,单个文件的打包和解包都实现了。要打包和解包整个目录,可以通过递归的方法实现,下面给出完整的代码: ============================================================
package main import (
"archive/tar"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"path"
) func main() {
TarFile := "test.tar"
src := "test"
dstDir := "test_ext" if err := Tar(src, TarFile, false); err != nil {
fmt.Println(err)
} if err := UnTar(TarFile, dstDir); err != nil {
fmt.Println(err)
}
} // 将文件或目录打包成 .tar 文件
// src 是要打包的文件或目录的路径
// dstTar 是要生成的 .tar 文件的路径
// failIfExist 标记如果 dstTar 文件存在,是否放弃打包,如果否,则会覆盖已存在的文件
func Tar(src string, dstTar string, failIfExist bool) (err error) {
// 清理路径字符串
src = path.Clean(src) // 判断要打包的文件或目录是否存在
if !Exists(src) {
return errors.New("要打包的文件或目录不存在:" + src)
} // 判断目标文件是否存在
if FileExists(dstTar) {
if failIfExist { // 不覆盖已存在的文件
return errors.New("目标文件已经存在:" + dstTar)
} else { // 覆盖已存在的文件
if er := os.Remove(dstTar); er != nil {
return er
}
}
} // 创建空的目标文件
fw, er := os.Create(dstTar)
if er != nil {
return er
}
defer fw.Close() // 创建 tar.Writer,执行打包操作
tw := tar.NewWriter(fw)
defer func() {
// 这里要判断 tw 是否关闭成功,如果关闭失败,则 .tar 文件可能不完整
if er := tw.Close(); er != nil {
err = er
}
}() // 获取文件或目录信息
fi, er := os.Stat(src)
if er != nil {
return er
} // 获取要打包的文件或目录的所在位置和名称
srcBase, srcRelative := path.Split(path.Clean(src)) // 开始打包
if fi.IsDir() {
tarDir(srcBase, srcRelative, tw, fi)
} else {
tarFile(srcBase, srcRelative, tw, fi)
} return nil
} // 因为要执行遍历操作,所以要单独创建一个函数
func tarDir(srcBase, srcRelative string, tw *tar.Writer, fi os.FileInfo) (err error) {
// 获取完整路径
srcFull := srcBase + srcRelative // 在结尾添加 "/"
last := len(srcRelative) - 1
if srcRelative[last] != os.PathSeparator {
srcRelative += string(os.PathSeparator)
} // 获取 srcFull 下的文件或子目录列表
fis, er := ioutil.ReadDir(srcFull)
if er != nil {
return er
} // 开始遍历
for _, fi := range fis {
if fi.IsDir() {
tarDir(srcBase, srcRelative+fi.Name(), tw, fi)
} else {
tarFile(srcBase, srcRelative+fi.Name(), tw, fi)
}
} // 写入目录信息
if len(srcRelative) > 0 {
hdr, er := tar.FileInfoHeader(fi, "")
if er != nil {
return er
}
hdr.Name = srcRelative if er = tw.WriteHeader(hdr); er != nil {
return er
}
} return nil
} // 因为要在 defer 中关闭文件,所以要单独创建一个函数
func tarFile(srcBase, srcRelative string, tw *tar.Writer, fi os.FileInfo) (err error) {
// 获取完整路径
srcFull := srcBase + srcRelative // 写入文件信息
hdr, er := tar.FileInfoHeader(fi, "")
if er != nil {
return er
}
hdr.Name = srcRelative if er = tw.WriteHeader(hdr); er != nil {
return er
} // 打开要打包的文件,准备读取
fr, er := os.Open(srcFull)
if er != nil {
return er
}
defer fr.Close() // 将文件数据写入 tw 中
if _, er = io.Copy(tw, fr); er != nil {
return er
}
return nil
} func UnTar(srcTar string, dstDir string) (err error) {
// 清理路径字符串
dstDir = path.Clean(dstDir) + string(os.PathSeparator) // 打开要解包的文件
fr, er := os.Open(srcTar)
if er != nil {
return er
}
defer fr.Close() // 创建 tar.Reader,准备执行解包操作
tr := tar.NewReader(fr) // 遍历包中的文件
for hdr, er := tr.Next(); er != io.EOF; hdr, er = tr.Next() {
if er != nil {
return er
} // 获取文件信息
fi := hdr.FileInfo() // 获取绝对路径
dstFullPath := dstDir + hdr.Name if hdr.Typeflag == tar.TypeDir {
// 创建目录
os.MkdirAll(dstFullPath, fi.Mode().Perm())
// 设置目录权限
os.Chmod(dstFullPath, fi.Mode().Perm())
} else {
// 创建文件所在的目录
os.MkdirAll(path.Dir(dstFullPath), os.ModePerm)
// 将 tr 中的数据写入文件中
if er := unTarFile(dstFullPath, tr); er != nil {
return er
}
// 设置文件权限
os.Chmod(dstFullPath, fi.Mode().Perm())
}
}
return nil
} // 因为要在 defer 中关闭文件,所以要单独创建一个函数
func unTarFile(dstFile string, tr *tar.Reader) error {
// 创建空文件,准备写入解包后的数据
fw, er := os.Create(dstFile)
if er != nil {
return er
}
defer fw.Close() // 写入解包后的数据
_, er = io.Copy(fw, tr)
if er != nil {
return er
} return nil
} // 判断档案是否存在
func Exists(name string) bool {
_, err := os.Stat(name)
return err == nil || os.IsExist(err)
} // 判断文件是否存在
func FileExists(filename string) bool {
fi, err := os.Stat(filename)
return (err == nil || os.IsExist(err)) && !fi.IsDir()
} // 判断目录是否存在
func DirExists(dirname string) bool {
fi, err := os.Stat(dirname)
return (err == nil || os.IsExist(err)) && fi.IsDir()
}
============================================================   如果要创建 .tar.gz 也很简单,只需要在创建 tar.Writer 或 tar.Reader 之前创建一个 gzip.Writer 或 gzip.Reader 就可以了,gzip.Writer 负责将 tar.Writer 中的数据压缩后写入文件,gzip.Reader 负责将文件中的数据解压后传递给 tar.Reader。要修改的部分如下: ============================================================
package main import (
// ...
"compress/gzip" // 这里导入 compress/gzip 包
// ...
) func Tar(src string, dstTar string, failIfExist bool) (err error) {
// ...
fw, er := os.Create(dstTar)
// ...
gw := gzip.NewWriter(fw) // 这里添加一个 gzip.Writer
// ...
tw := tar.NewWriter(gw) // 这里传入 gw
// ...
} func UnTar(srcTar string, dstDir string) (err error) {
// ...
fr, er := os.Open(srcTar)
// ...
gr, er := gzip.NewReader(fr) // 这里添加一个 gzip.Reader
// ...
tr := tar.NewReader(gr) // 这里传入 gr
// ...
}
============================================================   有个问题,用 golang 创建的 .tar 或 .tar.gz 文件无法在 Ubuntu 下用“归档管理器”修改,只能读取和解压,不知道为什么。

代码片段 - Golang 创建 .tar.gz 压缩包的更多相关文章

  1. LinuxPAServer19.0.tar.gz压缩包

    LinuxPAServer19.0.tar.gz DELPHI XE10.2(TOKYO)开始可以编写LINUX控制台程序.在LINUX上面需要部署LinuxPAServer19.0.tar.gz,即 ...

  2. CentOs下mysql-5.6.39-linux-glibc2.12-x86_64.tar.gz压缩包的安装

    之前写过一篇mysql在windows下的安装(猛击这儿),linux下用的比较少,最近切换到linux服务器了,发行mysql安装和windows下有所不同,只记录压缩包方式安装,rpm包类似 1. ...

  3. 通过maven-assembly-plugin将Springboot项目打包成tar.gz压缩包,在Linux环境可执行脚本直接安装成系统服务

    1.在pom.xml中添加maven-assembly-plugin依赖,同时需将默认生成的spring-boot-maven-plugin依赖删除,否则最终打出的发行包启动会有问题 <plug ...

  4. 代码片段 - Golang 实现简单的 Web 服务器

    ------------------------------ 下面一段代码,实现了最简单的 Web 服务器: 编译环境: Linux Mint 18 Cinnamon 64-bit Golang 1. ...

  5. 代码片段 - Golang 实现集合操作

    ------------------------------------------------------------ 如果用于多例程,可以使用下面的版本: -------------------- ...

  6. Ubuntu 12.10 安装 jdk-7u10-linux-x64.tar.gz(转载)

    在Ubuntu 12.10下安装 jdk-7u10-linux-x64.tar.gz 总的原则:将jdk-7u10-linux-x64.tar.gz压缩包解压至/usr/lib/jdk,设置jdk环境 ...

  7. 使用Java API进行tar.gz文件及文件夹压缩解压缩

    在java(JDK)中我们可以使用ZipOutputStream去创建zip压缩文件,(参考我之前写的文章 使用java API进行zip递归压缩文件夹以及解压 ),也可以使用GZIPOutputSt ...

  8. 几个个实用的PHP代码片段【自己备份】

    检查服务器是否是 HTTPS 这个PHP代码片段能够读取关于你服务器 SSL 启用(HTTPS)信息. if ($_SERVER['HTTPS'] != "on") { echo ...

  9. Windows如何压缩tar.gz格式

    Windows如何压缩tar.gz格式 tar.gz 是linux和unix下面比较常用的格式,几个命令就可以把文件压缩打包成tar.gz格式 然而这种格式在windows并不多见,WinRAR.Wi ...

随机推荐

  1. ionic 相关

     基本操作 $cordova platform update android@5.0.0 $ npm install -g cordova ionic $ ionic start myApp tabs ...

  2. 第二百零九天 how can I 坚持

    上班感觉好空虚啊. 今天感觉也没有什么,只是感觉上班的时候闲了一天,下班的时候就来事了. 确实没什么,只是上班的时候突然感觉好失落. 不该胡扯,朱镕基,言必行. 还有中国高铁谈判的新闻,中国确实是个比 ...

  3. component to string

    component to string string to component ObjectTextToBinary ObjectBinaryToText ReadComponent #include ...

  4. Axis2在Web项目中整合Spring

    一.说明: 上一篇说了Axis2与Web项目的整合(详情 :Axis2与Web项目整合)过程,如果说在Web项目中使用了Spring框架,那么又改如何进行Axis2相关的配置操作呢? 二.Axis2 ...

  5. C#下载http文件

    @(编程) using System; using System.IO; using System.Net; namespace Wisdombud.Util { public class HttpH ...

  6. PC端 $_SERVER 说明

    $_SERVER['PHP_SELF'] #当前正在执行脚本的文件名,与 document root相关. $_SERVER['argv'] #传递给该脚本的参数. $_SERVER['argc'] ...

  7. HDU 5458 Stability (树链剖分+并查集+set)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5458 给你n个点,m条边,q个操作,操作1是删边,操作2是问u到v之间的割边有多少条. 这题要倒着做才 ...

  8. http://docwiki.embarcadero.com/RADStudio/XE7/en/Delphi_Data_Types

    http://docwiki.embarcadero.com/RADStudio/XE7/en/Delphi_Data_Types

  9. Heritrix 3.1.0 源码解析(三十七)

    今天有兴趣重新看了一下heritrix3.1.0系统里面的线程池源码,heritrix系统没有采用java的cocurrency包里面的并发框架,而是采用了线程组ThreadGroup类来实现线程池的 ...

  10. Spring JTA应用JOTM & Atomikos III Atomikos

    前面简单介绍了JOTM如何在Spring中配置,并如何使用它的JTA事务,本节将介绍Atomikos如何与Spring集成,并使用它的JTA事务. Atomikos,是一个基于Java的开源事务管理器 ...