最近有个需求,需要写个脚本,但要编译为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.Name
if 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 = prefix
if 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.Name
if 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.Name
err = 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中。可以完美解决空目录不处理的问题。