Работа с файловой системой в Go: на практике
Работа с файловой системой — одна из базовых задач любого серверного приложения. Язык Go (Golang) предоставляет мощный и элегантный инструментарий для чтения, записи, удаления и управления файлами и директориями. В этой статье мы разберем практические примеры работы с файлами на Go, используя стандартные пакеты os, io/ioutil (до Go 1.16) и io. Вы узнаете, как эффективно управлять данными на диске, обрабатывать ошибки и писать чистый код.
1. Основы: открытие, чтение и запись файлов
Начнем с самых распространенных операций. Для открытия файла используется функция os.OpenFile или более простые обертки os.Create и os.Open. Рассмотрим пример чтения всего файла в память:
package main
import (
"fmt"
"os"
)
func main() {
// Чтение всего файла (до Go 1.16 использовали ioutil.ReadFile)
data, err := os.ReadFile("example.txt")
if err != nil {
fmt.Println("Ошибка чтения файла:", err)
return
}
fmt.Println("Содержимое файла:")
fmt.Println(string(data))
}
Для записи данных в файл используйте os.WriteFile:
package main
import (
"os"
)
func main() {
content := []byte("Привет, мир Go!\nВторая строка.")
err := os.WriteFile("output.txt", content, 0644)
if err != nil {
panic(err)
}
// Файл output.txt создан и содержит наш текст
}
Если вам нужно дописать данные в конец файла, используйте флаги:
file, err := os.OpenFile("log.txt", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
panic(err)
}
defer file.Close()
if _, err := file.WriteString("Новая запись в лог\n"); err != nil {
panic(err)
}
2. Работа с директориями: создание, удаление и обход
Управление папками — не менее важная задача. Создать новую директорию можно с помощью os.Mkdir (одна папка) или os.MkdirAll (рекурсивно, как mkdir -p):
package main
import (
"fmt"
"os"
)
func main() {
// Создаем одну папку
err := os.Mkdir("data", 0755)
if err != nil {
fmt.Println("Ошибка создания папки:", err)
}
// Рекурсивно создаем вложенную структуру
err = os.MkdirAll("data/subdir/logs", 0755)
if err != nil {
fmt.Println("Ошибка:", err)
}
}
Для обхода директории можно использовать функцию os.ReadDir (замена ioutil.ReadDir):
entries, err := os.ReadDir(".")
if err != nil {
panic(err)
}
for _, entry := range entries {
fmt.Printf("Имя: %s, Папка: %t\n", entry.Name(), entry.IsDir())
}
А для рекурсивного обхода всех файлов и папок используйте filepath.Walk из пакета path/filepath:
package main
import (
"fmt"
"os"
"path/filepath"
)
func main() {
err := filepath.Walk(".", func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
fmt.Println(path)
return nil
})
if err != nil {
fmt.Println("Ошибка обхода:", err)
}
}
Удаление файлов и папок выполняется функциями os.Remove и os.RemoveAll:
// Удалить один файл или пустую папку
os.Remove("temp.txt")
// Удалить папку со всем содержимым (аналог rm -rf)
os.RemoveAll("data")
3. Продвинутые техники: буферизованный ввод/вывод и работа с временными файлами
Для больших файлов неэффективно читать всё содержимое в память. Используйте буферизованное чтение с помощью bufio:
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
file, err := os.Open("largefile.txt")
if err != nil {
panic(err)
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
fmt.Println(line) // Обрабатываем строку
}
if err := scanner.Err(); err != nil {
fmt.Println("Ошибка чтения:", err)
}
}
Буферизованная запись также проста:
file, _ := os.Create("output.txt")
defer file.Close()
writer := bufio.NewWriter(file)
writer.WriteString("Буферизованная строка\n")
writer.Flush() // Важно! Сбрасываем буфер на диск
Временные файлы и папки создаются с помощью os.CreateTemp и os.MkdirTemp:
tmpFile, err := os.CreateTemp("", "prefix-*.txt")
if err != nil {
panic(err)
}
defer os.Remove(tmpFile.Name()) // Удаляем после работы
tmpFile.WriteString("Временные данные")
tmpFile.Close()
fmt.Println("Временный файл:", tmpFile.Name())
// Временная папка
tmpDir, _ := os.MkdirTemp("", "mydir-*")
defer os.RemoveAll(tmpDir)
4. Проверка существования файла и получение информации
Чтобы проверить, существует ли файл, используйте os.Stat в паре с os.IsNotExist:
func fileExists(path string) bool {
_, err := os.Stat(path)
return !os.IsNotExist(err)
}
func main() {
if fileExists("config.yaml") {
fmt.Println("Файл существует")
} else {
fmt.Println("Файл не найден")
}
}
Получить информацию о файле (размер, время изменения, права доступа) можно через os.Stat:
info, err := os.Stat("example.txt")
if err != nil {
panic(err)
}
fmt.Println("Размер:", info.Size(), "байт")
fmt.Println("Время изменения:", info.ModTime())
fmt.Println("Это папка?", info.IsDir())
fmt.Println("Права:", info.Mode())
Заключение
Мы рассмотрели клю