Golang建立 .tar.gz 壓縮包golang
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 下用「歸檔管理器」修改,只能讀取和解壓,不知道爲何。