Работа с ZIP-архивами в Go: полное руководство по пакету archive/zip

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Работа с 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,                    

Рекомендаций