To zip a file or a directory in Go using the standard library, use zip.Writer
type from the archive/zip
package. Creating a compressed archive using this method involves going through all the files you want to include, generating a local file header for each one, and writing its contents to the resulting ZIP file.
Check also our example on how to unzip a file in Go
package main
import (
"archive/zip"
"io"
"log"
"os"
"path/filepath"
)
func zipSource(source, target string) error {
// 1. Create a ZIP file and zip.Writer
f, err := os.Create(target)
if err != nil {
return err
}
defer f.Close()
writer := zip.NewWriter(f)
defer writer.Close()
// 2. Go through all the files of the source
return filepath.Walk(source, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
// 3. Create a local file header
header, err := zip.FileInfoHeader(info)
if err != nil {
return err
}
// set compression
header.Method = zip.Deflate
// 4. Set relative path of a file as the header name
header.Name, err = filepath.Rel(filepath.Dir(source), path)
if err != nil {
return err
}
if info.IsDir() {
header.Name += "/"
}
// 5. Create writer for the file header and save content of the file
headerWriter, err := writer.CreateHeader(header)
if err != nil {
return err
}
if info.IsDir() {
return nil
}
f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close()
_, err = io.Copy(headerWriter, f)
return err
})
}
func main() {
if err := zipSource("testFolder", "testFolder.zip"); err != nil {
log.Fatal(err)
}
if err := zipSource("testFolder/1/1.1/1.1.txt", "1.1.zip"); err != nil {
log.Fatal(err)
}
}
In the example above, we use a sample directory
testFolder
:testFolder ├── 1 │ └── 1.1 │ └── 1.1.txt ├── 2 └── test.txt
The zipSource(source, target string)
function does the following:
- Create a ZIP file and
zip.Writer
// 1. Create a ZIP file and zip.Writer f, err := os.Create(target) if err != nil { return err } defer f.Close() writer := zip.NewWriter(f) defer writer.Close()
In the first step, we need to create a ZIP file and initialize
zip.Writer
that writes the compressed data to the file. Notice that we defer closing the file and the writer to the end of thezipSource()
function. - Go through all the files of the source
// 2. Go through all the files of the source return filepath.Walk(source, func(path string, info os.FileInfo, err error) error { ... })
We want the
zipSource()
function to work on both a single file and a folder, so we need to walk through each file of thesource
path using thefilepath.Walk()
function. - Create a local file header
// 3. Create a local file header header, err := zip.FileInfoHeader(info) if err != nil { return err } // set compression header.Method = zip.Deflate
For each file in the archive, we need to create a local file header using the
FileInfoHeader()
function. The generated header does not have the compression method set, so we explicitly set it tozip.Deflate
. - Set the relative path of a file as the header name
// 4. Set relative path of a file as the header name header.Name, err = filepath.Rel(filepath.Dir(source), path) if err != nil { return err } if info.IsDir() { header.Name += "/" }
In the previous step, we created a file header using
FileInfoHeader(fi fs.FileInfo)
function with the name fromfs.FileInfo
argument. However, this is just a base name, so we need to modify it to preserve the directory structure in the generated ZIP. We do this by calculating the relative path from thesource
path directory to the specified filepath
usingfilepath.Dir()
andfilepath.Rel()
functions. For example, compressing the/a/b/c
directory with/a/b/c/d/test.txt
file gives us:filepath.Rel(filepath.Dir("/a/b/c"), "/a/b/c/d/test.txt")
which is equivalent to:
filepath.Rel("/a/b/", "/a/b/c/d/test.txt")
As a result, we get the relative path:
"c/d/test.txt"
Also, if the file is a directory, it should be marked with a trailing slash.
- Create writer for the file header and save content of the file
// 5. Create a writer for the file header and save the content of the file headerWriter, err := writer.CreateHeader(header) if err != nil { return err } if info.IsDir() { return nil } f, err := os.Open(path) if err != nil { return err } defer f.Close() _, err = io.Copy(headerWriter, f) return err
The last step is to create a writer for the specified file data based on the header. The data is transferred to the ZIP file using the
io.Copy()
function. Files that are directories should not be copied to avoid an error.