Работа с ZIP-архивами в Go (Golang): создание, чтение и извлечение файлов

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

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

Начать курс

Работа с ZIP-архивами в Go (Golang): полное руководство

В современной разработке на Go (Golang) работа с архивами — одна из частых задач. Будь то упаковка логов, загрузка файлов на сервер или создание резервных копий — стандартная библиотека archive/zip предоставляет всё необходимое для создания, чтения и модификации ZIP-архивов без установки сторонних зависимостей. В этой статье мы разберём основные операции с ZIP в Go с практическими примерами.

Основные возможности пакета archive/zip

Пакет archive/zip входит в стандартную библиотеку Go и поддерживает:

  • Создание новых ZIP-архивов с произвольной структурой папок
  • Чтение содержимого существующих архивов
  • Извлечение отдельных файлов или всего архива
  • Добавление файлов в существующий архив (через копирование)
  • Работу с метаданными файлов (дата, права доступа, комментарии)
  • Потоковую обработку (без загрузки всего архива в память)

Создание ZIP-архива в Go

Для создания архива используется zip.NewWriter(), который принимает io.Writer. Это позволяет записывать архив как в файл, так и в буфер (например, для отправки по HTTP).

package main

import (
    "archive/zip"
    "bytes"
    "fmt"
    "io"
    "log"
    "os"
)

func createZip() {
    // Создаём буфер для архива
    buf := new(bytes.Buffer)
    writer := zip.NewWriter(buf)

    // Добавляем файл с текстом
    file1, err := writer.Create("hello.txt")
    if err != nil {
        log.Fatal(err)
    }
    _, err = file1.Write([]byte("Привет, мир! Это файл из ZIP-архива."))
    if err != nil {
        log.Fatal(err)
    }

    // Добавляем файл в подпапку
    file2, err := writer.Create("docs/readme.md")
    if err != nil {
        log.Fatal(err)
    }
    _, err = file2.Write([]byte("# Документация\nСодержимое readme."))
    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")
}

func main() {
    createZip()
}

Чтение и извлечение файлов из ZIP

Для чтения архива используется zip.OpenReader() или zip.NewReader() (для данных из памяти). Каждый файл представлен структурой zip.File.

func readZip() {
    reader, err := zip.OpenReader("example.zip")
    if err != nil {
        log.Fatal(err)
    }
    defer reader.Close()

    for _, f := range reader.File {
        fmt.Printf("Файл: %s (размер: %d байт)\n", f.Name, f.UncompressedSize64)

        // Открываем содержимое файла
        rc, err := f.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)
    }
}

Извлечение всех файлов на диск

Часто требуется распаковать архив в указанную директорию. При этом важно корректно создавать подпапки.

func extractZip(src, dest string) {
    reader, err := zip.OpenReader(src)
    if err != nil {
        log.Fatal(err)
    }
    defer reader.Close()

    for _, f := range reader.File {
        // Путь для извлечения
        fpath := filepath.Join(dest, f.Name)

        // Проверяем, не выходит ли путь за пределы dest (безопасность)
        if !strings.HasPrefix(fpath, filepath.Clean(dest)+string(os.PathSeparator)) {
            log.Fatalf("Недопустимый путь: %s", fpath)
        }

        // Создаём папки
        if f.FileInfo().IsDir() {
            os.MkdirAll(fpath, os.ModePerm)
            continue
        }

        // Создаём родительскую папку
        os.MkdirAll(filepath.Dir(fpath), os.ModePerm)

        // Извлекаем файл
        rc, err := f.Open()
        if err != nil {
            log.Fatal(err)
        }

        outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
        if err != nil {
            rc.Close()
            log.Fatal(err)
        }

        _, err = io.Copy(outFile, rc)
        rc.Close()
        outFile.Close()

        if err != nil {
            log.Fatal(err)
        }
        fmt.Printf                    

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