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() }