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