最近有个需求,需要写个脚本,但要编译为exe可执行文件,首先考虑python打包,奈何使用pyinstaller打包后,出现各种各样的运行错误,最后放弃了,改为golang重写。因为要用到创建和解压zip文件,golang中使用zip模块的功能,远没有python那么方便。
一、压缩部分
把文件或者文件夹压缩为zip文件,主要过程就是创建目的zip文件,然后遍历源目录,将源目录下的文件拷贝到目的zip文件中,最重要的2个方法:
1、zip.newWriter 创建一个向zip文件中写入的writer
2、writer.CreateHeader 向zip文件头中写入内容
其过程网上一搜一大把,雷同的很多,果然大家都是代码的搬运工。在网上看到用的最多的一例,见原文连接:https://blog.csdn.net/lengyuezuixue/article/details/79651549,
import ("archive/zip""fmt""io""os""strings"
)//压缩文件
//files 文件数组,可以是不同dir下的文件或者文件夹
//dest 压缩文件存放地址
func Compress(files []*os.File, dest string) error {d, _ := os.Create(dest)defer d.Close()w := zip.NewWriter(d)defer w.Close()for _, file := range files {err := compress(file, "", w)if err != nil {return err}}return nil
}func compress(file *os.File, prefix string, zw *zip.Writer) error {info, err := file.Stat()if err != nil {return err}if info.IsDir() {prefix = prefix + "/" + info.Name()fileInfos, err := file.Readdir(-1)if err != nil {return err}for _, fi := range fileInfos {f, err := os.Open(file.Name() + "/" + fi.Name())if err != nil {return err}err = compress(f, prefix, zw)if err != nil {return err}}} else {header, err := zip.FileInfoHeader(info)header.Name = prefix + "/" + header.Nameif err != nil {return err}writer, err := zw.CreateHeader(header)if err != nil {return err}_, err = io.Copy(writer, file)file.Close()if err != nil {return err}}return nil
}
以上代码Compress方法在大多数情况下没有问题,但有一个缺陷,不能压缩空目录,源目录中含有空目录及嵌套的空目录,都不能被压缩。仔细读一遍代码可知,以上代码是通过递归遍历源目录中的文件,然后将文件copy到目的zip中,第57行。但没有考虑到空目录的情况,从第31行,IsDir后,对空目录没有做任何处理导致空目录被跳过。实例运行如图:
可以看到对空目录,以上代码没有进行处理。像将其优化,增加对非空目录的处理,完整如下:
import ("archive/zip""fmt""io""os""strings"
)//压缩文件
//src 可以是不同dir下的文件或者文件夹
//dest 压缩文件存放地址
func Compress(src string, dest string) error {f, err := os.Open(src)if err != nil {return err}files := []*os.File{f}d, _ := os.Create(dest)defer d.Close()w := zip.NewWriter(d)defer w.Close()for _, file := range files {err := compress(file, "", w)if err != nil {return err}}return nil
}func compress(file *os.File, prefix string, zw *zip.Writer) error {info, err := file.Stat()if err != nil {return err}if info.IsDir() {prefix = prefix + "/" + info.Name()fileInfos, err := file.Readdir(-1)if err != nil {return err}// 增加对空目录的判断if len(fileInfos) <= 0 {header, err := zip.FileInfoHeader(info)header.Name = prefixif err != nil {fmt.Println("error is:"+err.Error())return err}_, err = zw.CreateHeader(header)if err != nil {fmt.Println("create error is:"+err.Error())return err}file.Close()}for _, fi := range fileInfos {f, err := os.Open(file.Name() + "/" + fi.Name())if err != nil {return err}err = compress(f, prefix, zw)if err != nil {return err}}} else {header, err := zip.FileInfoHeader(info)header.Name = prefix + "/" + header.Nameif err != nil {return err}writer, err := zw.CreateHeader(header)if err != nil {return err}_, err = io.Copy(writer, file)file.Close()if err != nil {return err}}return nil
}//解压
func DeCompress(zipFile, dest string) error {reader, err := zip.OpenReader(zipFile)if err != nil {return err}defer reader.Close()for _, file := range reader.File {rc, err := file.Open()if err != nil {return err}defer rc.Close()filename := dest + file.Nameerr = os.MkdirAll(getDir(filename), 0755)if err != nil {return err}w, err := os.Create(filename)if err != nil {return err}defer w.Close()_, err = io.Copy(w, rc)if err != nil {return err}w.Close()rc.Close()}return nil
}func getDir(path string) string {return subString(path, 0, strings.LastIndex(path, "/"))
}func subString(str string, start, end int) string {rs := []rune(str)length := len(rs)if start < 0 || start > length {panic("start is wrong")}if end < start || end > length {panic("end is wrong")}return string(rs[start:end])
}
在43行-56行增加了对空目录的处理,直接将目录名写入到zip header中。可以完美解决空目录不处理的问题。