Работа с ZIP-архивами в Go (Golang)
В современной разработке на Go часто возникает необходимость работы с архивами — для упаковки логов, распространения конфигураций, бэкапов или передачи данных между микросервисами. Стандартная библиотека Go предоставляет мощный и гибкий пакет archive/zip, который позволяет создавать, читать и модифицировать ZIP-архивы без сторонних зависимостей. В этой статье мы подробно разберём все аспекты работы с ZIP-архивами в Go.
Обзор пакета archive/zip
Пакет archive/zip входит в стандартную библиотеку Go и поддерживает:
- Чтение и запись ZIP-архивов (формат PKWARE)
- Сжатие методом Deflate и Stored (без сжатия)
- Работу с файлами и директориями внутри архива
- Поддержку комментариев к архиву и отдельным файлам
- Установку атрибутов файлов (дата, права доступа)
- Потоковую обработку (архивы могут весить гигабайты)
Основные типы: zip.Writer для создания архивов и zip.Reader для чтения.
Создание ZIP-архива
Для создания архива используется zip.NewWriter, который принимает io.Writer. Каждый файл добавляется через Create или CreateHeader.
package main
import (
"archive/zip"
"bytes"
"fmt"
"io"
"log"
"os"
)
func main() {
// Создаём буфер для архива
buf := new(bytes.Buffer)
writer := zip.NewWriter(buf)
// Добавляем текстовый файл
file1, err := writer.Create("hello.txt")
if err != nil {
log.Fatal(err)
}
_, err = file1.Write([]byte("Hello, Go ZIP!"))
if err != nil {
log.Fatal(err)
}
// Добавляем файл с поддиректорией
file2, err := writer.Create("config/settings.json")
if err != nil {
log.Fatal(err)
}
_, err = file2.Write([]byte(`{"version": 1, "debug": true}`))
if err != nil {
log.Fatal(err)
}
// Обязательно закрываем writer
err = writer.Close()
if err != nil {
log.Fatal(err)
}
// Сохраняем архив на диск
err = os.WriteFile("example.zip", buf.Bytes(), 0644)
if err != nil {
log.Fatal(err)
}
fmt.Println("Архив создан: example.zip")
}
Чтение и извлечение файлов из архива
Для чтения архива используется zip.NewReader, которому передаётся io.ReaderAt и размер архива. Можно читать как в память, так и потоково.
func readArchive() {
// Открываем файл архива
reader, err := zip.OpenReader("example.zip")
if err != nil {
log.Fatal(err)
}
defer reader.Close()
// Перебираем все файлы в архиве
for _, file := range reader.File {
fmt.Printf("Файл: %s (размер: %d байт)\n", file.Name, file.UncompressedSize64)
// Открываем файл внутри архива
rc, err := file.Open()
if err != nil {
log.Fatal(err)
}
// Читаем содержимое
content, err := io.ReadAll(rc)
if err != nil {
log.Fatal(err)
}
rc.Close()
fmt.Printf("Содержимое: %s\n\n", content)
}
}
Добавление файлов с метаданными
С помощью zip.FileHeader можно задать дату, права доступа и метод сжатия:
func addFileWithMetadata(w *zip.Writer, name, content string) error {
header := &zip.FileHeader{
Name: name,
Method: zip.Deflate, // Используем Deflate (сжатие)
}
// Устанавливаем время модификации
header.SetModTime(time.Now())
// Устанавливаем права доступа (Unix rw-r--r--)
header.SetMode(0644)
f, err := w.CreateHeader(header)
if err != nil {
return err
}
_, err = f.Write([]byte(content))
return err
}
Извлечение архива в директорию
Практический пример распаковки архива с сохранением структуры каталогов:
func extractZip(src, dest string) error {
r, err := zip.OpenReader(src)
if err != nil {
return err
}
defer r.Close()
for _, f := range r.File {
// Формируем путь для извлечения
fpath := filepath.Join(dest, f.Name)
// Проверяем на Path Traversal
if !strings.HasPrefix(fpath,