Golang中如何创建包含目录的ZIP文件
Golang中如何创建包含目录的ZIP文件 大家好,
我正在尝试编写一个压缩目录为ZIP格式的小程序。 代码如下:
package main
import (
"archive/zip"
"fmt"
"io"
"io/fs"
"log"
"os"
"path/filepath"
"time"
)
var (
writer *zip.Writer
)
func main() {
fmt.Println("Hello from Dir Walker")
args := os.Args[1:]
if len(args) < 1 {
log.Fatal("Missing input arguments")
}
f, err := os.Create(args[1])
if err != nil {
log.Fatal("Error:", err)
}
defer f.Close()
writer = zip.NewWriter(f)
defer writer.Close()
start := time.Now()
if err := filepath.WalkDir(args[0], walker); err != nil {
log.Printf("Error in walker: %s\n", err)
}
t := time.Now()
elapsed := t.Sub(start)
fmt.Printf("Elapsed time: %v\n", elapsed)
}
func walker(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
info, err := d.Info()
if err != nil {
return err
}
header, err := zip.FileInfoHeader(info)
if err != nil {
return err
}
header.Method = zip.Deflate
header.Name = path
hw, err := writer.CreateHeader(header)
if err != nil {
return err
}
if d.IsDir() || (d.Type()&fs.ModeSymlink != 0) {
return nil
}
f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close()
_, err = io.Copy(hw, f)
return err
}
如果目录只包含文件,代码运行良好。 但当目录包含子目录时,我在命令行解压时会遇到错误:
» unzip ../output.zip massimoc@massimo-mbp
Archive: ../output.zip
inflating: _
inflating: a
checkdir error: a exists but is not directory
unable to process a/b.
checkdir error: a exists but is not directory
unable to process a/b/c.
checkdir error: a exists but is not directory
unable to process a/b/c/d.
checkdir error: a exists but is not directory
unable to process a/b/c/d/test.txt.
inflating: cmd
checkdir error: cmd exists but is not directory
unable to process cmd/zipper.
checkdir error: cmd exists but is not directory
unable to process cmd/zipper/main.go.
inflating: go.mod
inflating: walk_dir
inflating: walk_dir.go
看起来目录被添加了,但没有被标记为目录。
我对Go语言还比较陌生,所以我确信我遗漏了一些简单的东西。
更多关于Golang中如何创建包含目录的ZIP文件的实战教程也可以访问 https://www.itying.com/category-94-b0.html
我找到了解决方案,添加了
if d.IsDir() {
header.Name += "/"
}
Create() 的文档说明
要创建目录而非文件,请在名称末尾添加斜杠。
但 CreateHeader() 的文档中并未提及这一点。
更多关于Golang中如何创建包含目录的ZIP文件的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
问题在于你的代码没有正确处理目录条目。当创建ZIP文件时,目录需要特殊的处理方式。以下是修复后的代码:
package main
import (
"archive/zip"
"fmt"
"io"
"io/fs"
"log"
"os"
"path/filepath"
"strings"
"time"
)
func main() {
fmt.Println("Hello from Dir Walker")
args := os.Args[1:]
if len(args) < 2 {
log.Fatal("Usage: program <source_dir> <output_zip>")
}
srcDir := args[0]
outputFile := args[1]
f, err := os.Create(outputFile)
if err != nil {
log.Fatal("Error creating zip file:", err)
}
defer f.Close()
writer := zip.NewWriter(f)
defer writer.Close()
start := time.Now()
// 使用正确的相对路径处理
err = filepath.WalkDir(srcDir, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
// 获取相对于源目录的路径
relPath, err := filepath.Rel(srcDir, path)
if err != nil {
return err
}
// 跳过源目录本身
if relPath == "." {
return nil
}
// 确保目录路径以斜杠结尾
if d.IsDir() {
relPath += "/"
}
info, err := d.Info()
if err != nil {
return err
}
header, err := zip.FileInfoHeader(info)
if err != nil {
return err
}
// 设置正确的文件名(使用相对路径)
header.Name = filepath.ToSlash(relPath)
// 对于目录,设置正确的标志
if d.IsDir() {
header.Method = zip.Store
} else {
header.Method = zip.Deflate
}
hw, err := writer.CreateHeader(header)
if err != nil {
return err
}
// 如果是目录或符号链接,不需要写入内容
if d.IsDir() || (d.Type()&fs.ModeSymlink != 0) {
return nil
}
// 处理普通文件
f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close()
_, err = io.Copy(hw, f)
return err
})
if err != nil {
log.Printf("Error walking directory: %s\n", err)
}
t := time.Now()
elapsed := t.Sub(start)
fmt.Printf("Elapsed time: %v\n", elapsed)
}
关键修改点:
-
目录路径处理:为目录路径添加斜杠后缀(
relPath += "/"),这是ZIP格式中标识目录的标准方式。 -
相对路径:使用
filepath.Rel()获取相对于源目录的路径,避免在ZIP文件中包含绝对路径。 -
目录压缩方法:为目录设置
zip.Store方法(不压缩),因为目录条目没有实际内容需要压缩。 -
路径分隔符:使用
filepath.ToSlash()确保使用正斜杠作为路径分隔符,这是ZIP文件的标准。 -
参数检查:添加了更清晰的参数检查和使用说明。
-
跳过源目录:添加逻辑跳过源目录本身(当
relPath == "."时)。
这里还有一个更简洁的版本,使用archive/zip包的CreateRaw方法:
func walker(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
// 获取相对路径
relPath, err := filepath.Rel(srcDir, path)
if err != nil {
return err
}
if relPath == "." {
return nil
}
// 转换为ZIP格式的路径(使用正斜杠)
zipPath := filepath.ToSlash(relPath)
// 如果是目录,确保以斜杠结尾
if d.IsDir() {
zipPath += "/"
}
info, err := d.Info()
if err != nil {
return err
}
// 使用Create而不是CreateHeader来简化处理
if d.IsDir() {
_, err := writer.Create(zipPath)
return err
}
hw, err := writer.Create(zipPath)
if err != nil {
return err
}
f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close()
_, err = io.Copy(hw, f)
return err
}
使用writer.Create()方法会自动处理文件头信息,包括正确设置目录标志。对于目录,只需要创建一个空的条目即可。

