easy way to unzip file

huangapple go评论113阅读模式
英文:

easy way to unzip file

问题

有没有一种简单的方法来使用Go解压文件?

现在我的代码是:

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
}
英文:

Is there a easy way to unzip file with Go?

right now my code is:

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
}

答案1

得分: 69

对 OP 的解决方案进行了轻微修改,如果不存在,则创建包含目录 dest,并将文件提取/写入包装在闭包中,以消除每个 defer .Close() 调用的堆叠,根据 @Nick Craig-Wood 的评论:

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

    os.MkdirAll(dest, 0755)

    // 闭包解决所有延迟的 .Close() 方法的文件描述符问题
    extractAndWriteFile := func(f *zip.File) error {
        rc, err := f.Open()
        if err != nil {
            return err
        }
        defer func() {
            if err := rc.Close(); err != nil {
                panic(err)
            }
        }()

        path := filepath.Join(dest, f.Name)

        // 检查 ZipSlip(目录遍历)
        if !strings.HasPrefix(path, filepath.Clean(dest) + string(os.PathSeparator)) {
            return fmt.Errorf("非法文件路径:%s", path)
        }

        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
}

注意更新以包括 Close() 错误处理如果我们正在寻找最佳实践那么最好遵循所有最佳实践)。

<details>
<summary>英文:</summary>

Slight rework of the OP&#39;s solution to create the containing directory `dest` if it doesn&#39;t exist, and to wrap the file extraction/writing in a closure to eliminate stacking of `defer .Close()` calls per [@Nick Craig-Wood](https://stackoverflow.com/users/164234/nick-craig-wood)&#39;s comment:

    func Unzip(src, dest string) error {
        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 {
            rc, err := f.Open()
            if err != nil {
                return err
            }
            defer func() {
                if err := rc.Close(); err != nil {
                    panic(err)
                }
            }()

            path := filepath.Join(dest, f.Name)

            // Check for ZipSlip (Directory traversal)
            if !strings.HasPrefix(path, filepath.Clean(dest) + string(os.PathSeparator)) {
                return fmt.Errorf(&quot;illegal file path: %s&quot;, path)
            }

            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
    }

**Note:** Updated to include Close() error handling as well (if we&#39;re looking for best practices, may as well follow ALL of them).

</details>



# 答案2
**得分**: 6

我正在使用`archive/zip`包来读取.zip文件并将其复制到本地磁盘以下是解压.zip文件的源代码以满足我的需求

```go
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
}

以上是解压.zip文件的源代码。

英文:

I'm using archive/zip package to read .zip files and copy to the local disk. Below is the source code to unzip .zip files for my own needs.

import (
&quot;archive/zip&quot;
&quot;io&quot;
&quot;log&quot;
&quot;os&quot;
&quot;path/filepath&quot;
&quot;strings&quot;
)
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 &gt; -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
}

答案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)
}

更好的示例代码可以在这里找到[示例代码](https://stackoverflow.com/questions/20330210/golang-1-2-unzip-password-protected-zip-file)

然而如果不能使用7zip那么可以尝试以下方法使用defer和recover来捕获panic。(示例代码[链接](https://github.com/Unknwon/build-web-application-with-golang_EN/blob/master/code/src/apps/ch.2.3/panic_and_recover/main.go))

```go
func checkError(e error){
  if e != nil {
    panic(e)
  }
}
func cloneZipItem(f *zip.File, dest string){
    // 创建完整的目录路径
    path := filepath.Join(dest, f.Name)
    fmt.Println("Creating", path)
    err := os.MkdirAll(filepath.Dir(path), os.ModeDir|os.ModePerm)
    checkError(err)

    // 如果项目是文件,则进行克隆
    rc, err := f.Open()
    checkError(err)
    if !f.FileInfo().IsDir() {
        // 使用os.Create(),因为Zip文件不存储文件权限。
        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)
    }
}

以上是翻译好的内容,请确认是否满意。

英文:

I would prefer using 7zip with Go, which would give you something like this.

func extractZip() {
fmt.Println(&quot;extracting&quot;, 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)
}

Better example code

However, if using 7zip isn't possible, then try this. Defer a recover to catch the panics. (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(&quot;Creating&quot;, 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&#39;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

得分: 3

大多数答案都是错误的,因为它们假设Zip归档将发出目录条目。Zip归档不需要发出目录条目,因此对于这样的归档,一个简单的程序在遇到第一个非根文件时将崩溃,因为永远不会创建任何目录,文件创建将失败,因为尚未创建目录。这里是一个不同的方法:

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")
}
英文:

Most answers here are wrong, as they assume that the Zip archive will emit
directory entries. Zip archives are not required to emit directory
entries
, and so given an archive such as that, a naive program will crash
upon encountering the first non-root file, as no directories will ever be
created, and the file creation will fail as the directory has not been created
yet. Here is a different approach:

package main
import (
&quot;archive/zip&quot;
&quot;os&quot;
&quot;path&quot;
)
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(&quot;Microsoft.VisualCpp.Tools.HostX64.TargetX64.vsix&quot;, &quot;tools&quot;)
}

答案5

得分: 2

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

你可以尝试使用io.Copy(src, dest)来简化这个过程,但我还没有进行过测试。

例如:

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

老实说,对我来说,你的代码看起来很不错,如果我要自己编写一个提取函数(如果上述方法不起作用),我可能会借鉴你的方法。

英文:

I have been doing some browsing of google and repeatedly found people saying that there is no library that can handle that. Maybe I missed a custom repository in my search though and someone else will find it for us.

You may be able to make use of io.Copy(src, dest) to ease the process but I haven't tested it at all.

For instance:

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

Honestly to me your code looks pretty nice and if I were to do an Extract function myself (and the above doesn't work) then I would probably take a page from your book.

答案6

得分: 2

在为LGTM.com上的Go代码查找ZipSlip漏洞的查询中(我是其中的一名开发者),我注意到几个项目中存在类似于被接受答案的代码,例如rclone

正如@woogoo指出的那样,这段代码存在ZipSlip漏洞,因此我认为答案应该更新为类似以下代码(代码取自rclone修复):

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)

    // 闭包用于解决所有延迟的.Close()方法中的文件描述符问题
    extractAndWriteFile := func(f *zip.File) error {
        path := filepath.Join(dest, f.Name)
        // 检查ZipSlip漏洞:https://snyk.io/research/zip-slip-vulnerability
        if !strings.HasPrefix(path, dest) {
            return fmt.Errorf("%s: 非法文件路径", 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
}
英文:

While working on the query for catching ZipSlip vulnerabilities in Go on LGTM.com (of which I am a developer), I noticed code similar to the accepted answer in several projects, e.g. rclone.

As @woogoo has pointed out, this code is vulnerable to ZipSlip, so I believe the answer should be updated to something like the following (code taken from 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(&quot;%s: illegal file path&quot;, 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
}

答案7

得分: -2

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
}
英文:
package main
import (
&quot;os&quot;
&quot;io&quot;
&quot;io/ioutil&quot;
&quot;fmt&quot;
&quot;strings&quot;
&quot;archive/zip&quot;
&quot;path/filepath&quot;
)
func main() {
if err := foo(&quot;test.zip&quot;); err != nil {
fmt.Println(err)
}
}
func foo(yourZipFile string) error {
tmpDir, err := ioutil.TempDir(&quot;/tmp&quot;, &quot;yourPrefix-&quot;)
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(&quot;%s: illegal file path&quot;, 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(&quot;zip file is empty&quot;)
}
return filenames, nil
}

huangapple
  • 本文由 发表于 2013年12月4日 01:00:54
  • 转载请务必保留本文链接:https://go.coder-hub.com/20357223.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定