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

I want to write a tar_gz tool in Go. The input is just like linux command:

$tar czvf targetFileName inputDirectoryPath

Suppose I have an inputDirectory structured as below:

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

For Example: use command:

$tar czvf test.tar.gz test/

we can tar and gzip the whole test directory.


My Problem is I can write a tar and gz route to recursively iterate all the file in test directory and write the file to test.tar.gz file. But I don't know how to write a directory to the test.tar.gz. After running my program, the structure in test.tar.gz file is:

0.txt
1.txt

Can anyone tell me how to write the directory recursively to the output tar.gz file. Thanks a lot.

    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
", 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" )
    }

You're only adding the filename to the tar, not the entire path. You need to keep the whole path for Tar to be able to understand directories. You just need to change one line:

h.Name = fi.Name()

Should be:

h.Name = _path

On Linux, the output of 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

An alternative is to use the built in filepath.Walk function

// 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
}