英文:
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's solution to create the containing directory `dest` if it doesn'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)'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("illegal file path: %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
}
**Note:** Updated to include Close() error handling as well (if we'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 (
"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
}
答案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("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)
}
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("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
得分: 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 (
"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")
}
答案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("%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
}
答案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 (
"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
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论