如何在Golang中将目录(不仅仅是其中的文件)写入tar.gz文件

11

我希望使用Go语言编写一个tar_gz工具,输入格式类似于Linux命令:

$tar czvf targetFileName inputDirectoryPath

假设我有一个如下结构的inputDirectory:

test [dir]
-- 0.txt
    -- 1 [sub dir]
         -- 1.txt

例如:使用命令:

$tar czvf test.tar.gz test/

我们可以将整个测试目录打包并压缩成tar.gz文件。


我的问题是,我能够编写一个tar和gz路由来递归迭代测试目录中的所有文件,并将文件写入test.tar.gz文件,但我不知道如何将一个目录写入test.tar.gz文件。运行程序后,test.tar.gz文件中的结构如下:

0.txt
1.txt

有谁能告诉我如何将目录递归地写入输出 tar.gz 文件?非常感谢。

    package main

    import (
      "fmt"
      "os"
      "io"
      "log"
      "strings"
      "archive/tar"
      "compress/gzip"
    )

    func handleError( _e error ) {
      if _e != nil {
        log.Fatal( _e )
      }
    }

    func TarGzWrite( _path string, tw *tar.Writer, fi os.FileInfo ) {
      fr, err := os.Open( _path )
      handleError( err )
      defer fr.Close()

      h := new( tar.Header )
      h.Name = fi.Name()
      h.Size = fi.Size()
      h.Mode = int64( fi.Mode() )
      h.ModTime = fi.ModTime()

      err = tw.WriteHeader( h )
      handleError( err )

      _, err = io.Copy( tw, fr )
      handleError( err )
    }

    func IterDirectory( dirPath string, tw *tar.Writer ) {
      dir, err := os.Open( dirPath )
      handleError( err )
      defer dir.Close()
      fis, err := dir.Readdir( 0 )
      handleError( err )
      for _, fi := range fis {
        curPath := dirPath + "/" + fi.Name()
        if fi.IsDir() {
          //TarGzWrite( curPath, tw, fi )
          IterDirectory( curPath, tw )
        } else {
          fmt.Printf( "adding... %s\n", curPath )
          TarGzWrite( curPath, tw, fi )
        }
      }
    }

    func TarGz( outFilePath string, inPath string ) {
      // file write
      fw, err := os.Create( outFilePath )
      handleError( err )
      defer fw.Close()

      // gzip write
      gw := gzip.NewWriter( fw )
      defer gw.Close()

      // tar write
      tw := tar.NewWriter( gw )
      defer tw.Close()

      IterDirectory( inPath, tw )

      fmt.Println( "tar.gz ok" )
    }

    func main() {
      targetFilePath := "test.tar.gz"
      inputDirPath := "test/"
      TarGz( targetFilePath, strings.TrimRight( inputDirPath, "/" ) )
      fmt.Println( "Hello, World" )
    }
2个回答

13

你只是在tar中添加文件名,而不是整个路径。你需要保留完整的路径以便Tar能够理解目录结构。你只需要改变一行代码:

h.Name = fi.Name()

应该是:

h.Name = _path

在Linux上,运行 tar -tvf test.tar.gz 命令的输出:

-rw-rw-r-- 0/0               0 2012-11-28 11:17 test/0.txt
-rw-rw-r-- 0/0               0 2012-11-28 11:17 test/sub/1.txt

6
一种替代方法是使用内置的filepath.Walk函数。
// root_directory has been set further up

walkFn := func(path string, info os.FileInfo, err error) error {
    if info.Mode().IsDir() {
        return nil
    }
    // Because of scoping we can reference the external root_directory variable
    new_path := path[len(root_directory):]
    if len(new_path) == 0 {
        return nil
    }
    fr, err := os.Open(path)
    if err != nil {
        return err
    }
    defer fr.Close()

    if h, err := tar.FileInfoHeader(info, new_path); err != nil {
        log.Fatalln(err)
    } else {
        h.Name = new_path
        if err = tw.WriteHeader(h); err != nil {
            log.Fatalln(err)
        }
    }
    if length, err := io.Copy( tw, fr ); err != nil {
        log.Fatalln(err)
    } else {
        fmt.Println(length)
    }
    return nil
}

if err = filepath.Walk(root_directory, walkFn); err != nil {
    return err
}

2
我认为 new_path := path[len(root_directory)+1:] 更好,可以避免每个文件的前缀加上 / 。 - DarKnight
它不应该仅对 .IsDir() 返回 nil,而对于例如符号链接也应该返回 nil 吧?所以也许只需要 if !info.Mode().IsRegular() - Wolfson
关于 defer fr.Close():在这种情况下,是包围函数 walkFn 还是 filepath.Walk()?换句话说,在 walkFn 函数末尾加上 fr.Close() 是否更合理,以确保文件在 walkFn 函数结束时关闭? - Wolfson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接