Работа с файловой системой в Go: от основ до продвинутых техник
Go (Golang) — это современный компилируемый язык программирования, который славится своей производительностью, простотой и встроенной поддержкой работы с файловой системой. Возможность эффективно читать, записывать, создавать и управлять файлами и директориями — один из ключевых навыков для любого разработчика на Go. В этой статье мы подробно разберем основные операции с файловой системой, используя стандартную библиотеку os, io/ioutil (начиная с Go 1.16 рекомендуется использовать os и io) и path/filepath. Вы узнаете, как открывать и закрывать файлы, читать и записывать данные, работать с директориями и обходить файловые деревья.
1. Основы: открытие, чтение и запись файлов
Работа с файлами в Go начинается с пакета os. Чтобы открыть файл, используется функция os.Open (только для чтения) или os.OpenFile (для чтения и записи с указанием режима). После завершения работы файл обязательно нужно закрыть с помощью defer file.Close(), чтобы освободить системные ресурсы.
Чтение файла целиком
Самый простой способ прочитать файл — использовать os.ReadFile (доступно с Go 1.16). Он возвращает срез байтов и ошибку.
package main
import (
"fmt"
"os"
)
func main() {
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("Привет, мир!\nЭто строка для записи.")
err := os.WriteFile("output.txt", content, 0644)
if err != nil {
panic(err)
}
}
Обратите внимание на третий аргумент — 0644. Это права доступа к файлу в Unix-стиле (владелец может читать и писать, остальные — только читать).
Чтение и запись с буферизацией
Для больших файлов или потоковой обработки используйте bufio:
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
file, err := os.Open("large.txt")
if err != nil {
panic(err)
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
fmt.Println(scanner.Text())
}
if err := scanner.Err(); err != nil {
fmt.Println("Ошибка сканирования:", err)
}
}
2. Работа с директориями
Go предоставляет удобные функции для создания, удаления и чтения директорий. Основные из них находятся в пакете os.
Создание директории
Для создания одной директории используйте os.Mkdir, для вложенных — os.MkdirAll.
package main
import (
"os"
)
func main() {
// Создаем одну директорию
err := os.Mkdir("newdir", 0755)
if err != nil {
panic(err)
}
// Создаем вложенные директории
err = os.MkdirAll("path/to/nested/dir", 0755)
if err != nil {
panic(err)
}
}
Чтение содержимого директории
Функция os.ReadDir возвращает список элементов в директории (начиная с Go 1.16).
package main
import (
"fmt"
"os"
)
func main() {
entries, err := os.ReadDir(".")
if err != nil {
panic(err)
}
for _, entry := range entries {
fmt.Println(entry.Name(), entry.IsDir())
}
}
Удаление файлов и директорий
Для удаления используйте os.Remove (для файла или пустой директории) и os.RemoveAll (для директории со всем содержимым).
package main
import (
"os"
)
func main() {
// Удалить файл
err := os.Remove("temp.txt")
if err != nil {
panic(err)
}
// Удалить директорию со всем содержимым
err = os.RemoveAll("old_project")
if err != nil {
panic(err)
}
}
3. Обход файловой системы
Часто требуется рекурсивно обойти все файлы и папки в директории. Для этого в Go есть пакет path/filepath с функцией filepath.Walk (или более производительная filepath.WalkDir с Go 1.16).
Использование filepath.WalkDir
Функция принимает корневую директорию и callback-функцию, которая вызывается для каждого элемента.
package main
import (
"fmt"
"os"
"path/filepath"
)
func main() {
root := "."
err := filepath.WalkDir(root, func(path string, d os.DirEntry, err error) error {
if err != nil {
return err
}
fmt.Printf("Путь: %s, Размер: ", path)
if !d.IsDir() {
info, _ := d.Info()
fmt.Printf("%d байт", info.Size())
} else {
fmt.Print("[директория]")
}
fmt.Println()
return nil
})
if err != nil {
fmt.Println("Ошибка обхода:", err)
}
}
Этот пример выводит все файлы и папки, начиная с текущей директории, с указанием размера для файлов. WalkDir быстрее, чем Walk, так как не вызывает os.Lstat для каждого элемента, если это не нужно.
4. Продвинутые техники: проверка существования и работа с временными файлами
Помимо базовых операций, часто нужно проверять, существует ли файл, или создавать временные файлы для безопасной работы.
Проверка существования файла
Используйте os.Stat и проверяйте ошибку с помощью errors.Is(err, os.ErrNotExist).
package main
import (
"errors"
"fmt"
"os"
)
func fileExists(filename string) bool {
_, err := os.Stat(filename)