使用golang解压缩文件的简便方法

时间:2013-12-03 17:00:55

标签: go zip unzip

有一种简单的方法可以用golang解压缩文件吗?

现在我的代码是:

func Unzip(src, dest string) error {
    r, err := zip.OpenReader(src)
    if err != nil {
        return err
    }
    defer r.Close()

    for _, f := range r.File {
        rc, err := f.Open()
        if err != nil {
            return err
        }
        defer rc.Close()

        path := filepath.Join(dest, f.Name)
        if f.FileInfo().IsDir() {
            os.MkdirAll(path, f.Mode())
        } else {
            f, err := os.OpenFile(
                path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
            if err != nil {
                return err
            }
            defer f.Close()

            _, err = io.Copy(f, rc)
            if err != nil {
                return err
            }
        }
    }

    return nil
}

8 个答案:

答案 0 :(得分:37)

如果OP目录不存在,OP的解决方案稍微修改以创建包含目录dest,并将文件提取/写入包装在一个闭包中以消除{{1每个@Nick Craig-Wood的评论调用:

defer .Close()

注意:已更新以包含Close()错误处理(如果我们正在寻找最佳做法,也可以按照所有这些做法)。

答案 1 :(得分:6)

我正在使用archive/zip包来读取.zip文件并复制到本地磁盘。下面是根据自己的需要解压缩.zip文件的源代码。

import (
    "archive/zip"
    "io"
    "log"
    "os"
    "path/filepath"
    "strings"
)

func unzip(src, dest string) error {
    r, err := zip.OpenReader(src)
    if err != nil {
        return err
    }
    defer r.Close()

    for _, f := range r.File {
        rc, err := f.Open()
        if err != nil {
            return err
        }
        defer rc.Close()

        fpath := filepath.Join(dest, f.Name)
        if f.FileInfo().IsDir() {
            os.MkdirAll(fpath, f.Mode())
        } else {
            var fdir string
            if lastIndex := strings.LastIndex(fpath,string(os.PathSeparator)); lastIndex > -1 {
                fdir = fpath[:lastIndex]
            }

            err = os.MkdirAll(fdir, f.Mode())
            if err != nil {
                log.Fatal(err)
                return err
            }
            f, err := os.OpenFile(
                fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
            if err != nil {
                return err
            }
            defer f.Close()

            _, err = io.Copy(f, rc)
            if err != nil {
                return err
            }
        }
    }
    return nil
}

答案 2 :(得分:3)

我一直在浏览谷歌并反复发现有人说没有可以处理的库。也许我在我的搜索中错过了一个自定义存储库,而其他人会为我们找到它。

您可以使用io.Copy(src, dest)来简化流程,但我根本没有对其进行测试。

例如:

os.MkDirAll(dest, r.File.Mode)
d, _ := os.Open(dest)
io.Copy(r.File, d)

老实说,你的代码看起来很漂亮,如果我自己做一个提取功能(以上不起作用)那么我可能会从你的书中拿一页。

答案 3 :(得分:3)

我更喜欢和Go一起使用7zip,这会给你这样的东西。

func extractZip() {
    fmt.Println("extracting", zip_path)
    commandString := fmt.Sprintf(`7za e %s %s`, zip_path, dest_path)
    commandSlice := strings.Fields(commandString)
    fmt.Println(commandString)
    c := exec.Command(commandSlice[0], commandSlice[1:]...)
    e := c.Run()
    checkError(e)
}

更好example code

但是,如果无法使用7zip,请尝试此操作。推迟恢复以赶上恐慌。 (Example

func checkError(e error){
  if e != nil {
    panic(e)
  }
}
func cloneZipItem(f *zip.File, dest string){
    // Create full directory path
    path := filepath.Join(dest, f.Name)
    fmt.Println("Creating", path)
    err := os.MkdirAll(filepath.Dir(path), os.ModeDir|os.ModePerm)
    checkError(err)

    // Clone if item is a file
    rc, err := f.Open()
    checkError(err)
    if !f.FileInfo().IsDir() {
        // Use os.Create() since Zip don't store file permissions.
        fileCopy, err := os.Create(path)
        checkError(err)
        _, err = io.Copy(fileCopy, rc)
        fileCopy.Close()
        checkError(err)
    }
    rc.Close()
}
func Extract(zip_path, dest string) {
    r, err := zip.OpenReader(zip_path)
    checkError(err)
    defer r.Close()
    for _, f := range r.File {
        cloneZipItem(f, dest)
    }
}

答案 4 :(得分:2)

LGTM.com(我是一名开发人员)上处理Go上的ZipSlip漏洞的查询时,我注意到在一些项目中代码类似于公认的答案,例如rclone

正如@woogoo所指出的,此代码易受ZipSlip攻击,因此我认为答案应更新为以下内容(代码来自rclone fix):

func Unzip(src, dest string) error {
    dest = filepath.Clean(dest) + string(os.PathSeparator)

    r, err := zip.OpenReader(src)
    if err != nil {
        return err
    }
    defer func() {
        if err := r.Close(); err != nil {
            panic(err)
        }
    }()

    os.MkdirAll(dest, 0755)

    // Closure to address file descriptors issue with all the deferred .Close() methods
    extractAndWriteFile := func(f *zip.File) error {
        path := filepath.Join(dest, f.Name)
        // Check for ZipSlip: https://snyk.io/research/zip-slip-vulnerability
        if !strings.HasPrefix(path, dest) {
            return fmt.Errorf("%s: illegal file path", path)
        }

        rc, err := f.Open()
        if err != nil {
            return err
        }
        defer func() {
            if err := rc.Close(); err != nil {
                panic(err)
            }
        }()

        if f.FileInfo().IsDir() {
            os.MkdirAll(path, f.Mode())
        } else {
            os.MkdirAll(filepath.Dir(path), f.Mode())
            f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
            if err != nil {
                return err
            }
            defer func() {
                if err := f.Close(); err != nil {
                    panic(err)
                }
            }()

            _, err = io.Copy(f, rc)
            if err != nil {
                return err
            }
        }
        return nil
    }

    for _, f := range r.File {
        err := extractAndWriteFile(f)
        if err != nil {
            return err
        }
    }

    return nil
}

答案 5 :(得分:2)

这里的大多数答案都是错误的,因为他们假设 Zip 存档会发出 目录条目。 Zip 档案是 not required to emit directory entries,所以给定一个这样的档案,一个简单的程序会崩溃 遇到第一个非根文件时,因为永远不会有目录 创建,并且文件创建将失败,因为目录尚未创建 然而。这是一种不同的方法:

package main

import (
   "archive/zip"
   "os"
   "path"
)

func unzip(source, dest string) error {
   read, err := zip.OpenReader(source)
   if err != nil { return err }
   defer read.Close()
   for _, file := range read.File {
      if file.Mode().IsDir() { continue }
      open, err := file.Open()
      if err != nil { return err }
      name := path.Join(dest, file.Name)
      os.MkdirAll(path.Dir(name), os.ModeDir)
      create, err := os.Create(name)
      if err != nil { return err }
      defer create.Close()
      create.ReadFrom(open)
   }
   return nil
}

func main() {
   unzip("Microsoft.VisualCpp.Tools.HostX64.TargetX64.vsix", "tools")
}

答案 6 :(得分:0)

@Astockwell的代码中有Zip Slip Vulnerability,请参阅:https://snyk.io/research/zip-slip-vulnerability

答案 7 :(得分:0)

package main

import (
    "os"
    "io"
    "io/ioutil"
    "fmt"
    "strings"
    "archive/zip"
    "path/filepath"
)

func main() {
    if err := foo("test.zip"); err != nil {
        fmt.Println(err)
    }
}

func foo(yourZipFile string) error {
    tmpDir, err := ioutil.TempDir("/tmp", "yourPrefix-")
    if err != nil {
        return err
    }
    defer os.RemoveAll(tmpDir)
    if filenames, err := Unzip(yourZipFile, tmpDir); err != nil {
        return err
    } else {
        for f := range filenames {
            fmt.Println(filenames[f])
        }
    }
    return nil
}


func Unzip(src string, dst string) ([]string, error) {
    var filenames []string
    r, err := zip.OpenReader(src)
    if err != nil {
        return nil, err
    }
    defer r.Close()
    for f := range r.File {
        dstpath := filepath.Join(dst, r.File[f].Name)
        if !strings.HasPrefix(dstpath, filepath.Clean(dst) + string(os.PathSeparator)) {
            return nil, fmt.Errorf("%s: illegal file path", src)
        }
        if r.File[f].FileInfo().IsDir() {
            if err := os.MkdirAll(dstpath, os.ModePerm); err != nil {
                return nil, err
            }
        } else {
            if rc, err := r.File[f].Open(); err != nil {
                return nil, err
            } else {
                defer rc.Close()
                if of, err := os.OpenFile(dstpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, r.File[f].Mode()); err != nil {
                    return nil, err
                } else {
                    defer of.Close()
                    if _, err = io.Copy(of, rc); err != nil {
                        return nil, err
                    } else {
                        of.Close()
                        rc.Close()
                        filenames = append(filenames, dstpath)
                    }
                }
            }
        }
    }
    if len(filenames) == 0 {
        return nil, fmt.Errorf("zip file is empty")
    }
    return filenames, nil
}