在项目中需要使用到zip压缩与解压缩的功能,这类功能应该来说是很基础了,直接引用go官方的包archive/zip,对于如何压缩文件夹的功能不是很熟悉,参考别人的代码加入到项目中,在实际使用中发现有些不符合预期的地方。

问题

代码引用自博客——Go压缩解压文件夹

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
//zipDir("F:\\dumps", "F:\\dumps.zip")
func zipDir(dir, zipFile string) error {

fz, err := os.Create(zipFile)
if err != nil {
log.Debug("Create zip file failed: %s\n", err.Error())
return err
}
defer fz.Close()

w := zip.NewWriter(fz)
defer w.Close()

filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if !info.IsDir() {
fDest, err := w.Create(path[len(dir)+1:])
if err != nil {
log.Printf("Create failed: %s\n", err.Error())
return err
}
fSrc, err := os.Open(path)
if err != nil {
log.Printf("Open failed: %s\n", err.Error())
return err
}
defer fSrc.Close()
_, err = io.Copy(fDest, fSrc)
if err != nil {
log.Printf("Copy failed: %s\n", err.Error())
return err
}
}
return nil
})

return nil
}

压缩文件夹函数确实能够压缩文件夹,但实际使用中,我发现和我需要的效果有些出入,例如,我的目录结果为:

1
2
3
4
5
6
frist/
├── second_1
│   └── 1.txt
└── second_2
└── 1.txt
2 directories, 2 files

我需要连着frist顶级目录一起进行压缩,但事与愿违,即使传入frist的上一级目录,zipDir函数依然只会压缩second_1以及second_2;而且,当我输出的目标ZIP文件是压缩的文件夹中时,压缩包内会出现一个名为zip的临时文件。

解决

故此,我本着不应该没人做的更好的想法(嘿嘿~),找到了Golang: Working with ZIP archives

可以压缩顶层目录,且压缩文件即使要输出到压缩目录,也不会出现什么异常。

Compressing

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
// zipit("/tmp/documents", "/tmp/backup.zip")
// zipit("/tmp/report.txt", "/tmp/report-2015.zip")
func zipit(source, target string) error {
zipfile, err := os.Create(target)
if err != nil {
return err
}
defer zipfile.Close()

archive := zip.NewWriter(zipfile)
defer archive.Close()

info, err := os.Stat(source)
if err != nil {
return nil
}

var baseDir string
if info.IsDir() {
baseDir = filepath.Base(source)
}

filepath.Walk(source, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}

header, err := zip.FileInfoHeader(info)
if err != nil {
return err
}

if baseDir != "" {
header.Name = filepath.Join(baseDir, strings.TrimPrefix(path, source))
}

if info.IsDir() {
header.Name += "/"
} else {
header.Method = zip.Deflate
}

writer, err := archive.CreateHeader(header)
if err != nil {
return err
}

if info.IsDir() {
return nil
}

file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close()
_, err = io.Copy(writer, file)
return err
})

return err
}

2019-3-22 11:47:46

发现一个小问题,当传入相对路径./testDir给函数zipit时,会出现两个顶层目录,是因为这行代码:

1
header.Name = filepath.Join(baseDir, strings.TrimPrefix(path, source))

strings.TrimPrefix,去掉前缀

1
2
3
4
5
6
7
Code:
var s = "¡¡¡Hello, Gophers!!!"
s = strings.TrimPrefix(s, "¡¡¡Hello, ")
s = strings.TrimPrefix(s, "¡¡¡Howdy, ")
fmt.Print(s)
Output:
Gophers!!!

这行代码的意思也就是,去掉前缀的绝对路径,保留下要压缩文件的相对路径,但我们传入的是./testDir,相对于路径来说,多了./,所以其并不会去除成功,那么就会出现顶层目录出现两级的情况。

解决方法是:

  • 传入相对路径时,不加./
  • 检查传入的路径是否为绝对路径。
1
2
3
4
5
6
7
if isAbs := filepath.IsAbs(source); !isAbs {
//source, err = filepath.Abs(source) // 将传入路径直接转化为绝对路径
//if err != nil {
// return err
//}
source = filepath.Base(source) // 去掉字符“./”
}

加入以上代码可以解决问题,代码中,其实可以看到我屏蔽了——filepath.Abs函数解决方式,这个函数调用了syscall,也就是说借用了系统命令,这相对而言是比filepath.Base更消耗资源的,因为涉及到了语言系统交互的层面,类似于go执行shell命令

Extracting

附上zip解压缩的代码,以便以后查看。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// unzip("/tmp/report-2015.zip", "/tmp/reports/")
func unzip(archive, target string) error {
reader, err := zip.OpenReader(archive)
if err != nil {
return err
}

if err := os.MkdirAll(target, 0755); err != nil {
return err
}

for _, file := range reader.File {
path := filepath.Join(target, file.Name)
if file.FileInfo().IsDir() {
os.MkdirAll(path, file.Mode())
continue
}

fileReader, err := file.Open()
if err != nil {
return err
}
defer fileReader.Close()

targetFile, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, file.Mode())
if err != nil {
return err
}
defer targetFile.Close()

if _, err := io.Copy(targetFile, fileReader); err != nil {
return err
}
}

return nil
}

这里我再放置一篇博主的:Golang: Working with Gzip and Tar