Работа с файловой системой в Go: для начинающих
Go (Golang) — это современный компилируемый язык, который славится своей простотой, производительностью и встроенными инструментами. Одной из ключевых задач любого backend-разработчика является взаимодействие с файловой системой: чтение конфигураций, запись логов, создание временных файлов и директорий. В этой статье мы разберём основные операции с файлами и папками на Go, начиная с самых азов. Материал рассчитан на новичков, поэтому каждый шаг будет сопровождаться понятными примерами кода.
Основные пакеты для работы с файлами
В стандартной библиотеке Go есть два основных пакета для работы с файловой системой:
os— низкоуровневая работа с файлами, папками, правами доступа и процессами.io/ioutil(устарел в Go 1.16, но всё ещё встречается) — удобные функции для чтения/записи файлов целиком.io— интерфейсы для потокового ввода/вывода.bufio— буферизированное чтение/запись (полезно для больших файлов).
Начиная с Go 1.16, рекомендуется использовать функции из пакета os (os.ReadFile, os.WriteFile) вместо ioutil. Мы будем опираться на современный подход.
Чтение файлов
Самый простой способ прочитать небольшой файл целиком — использовать os.ReadFile. Он возвращает слайс байтов и ошибку.
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))
}
Если файл большой (например, лог-файл на сотни мегабайт), лучше читать его построчно или блоками с помощью bufio.Scanner:
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
file, err := os.Open("bigfile.log")
if err != nil {
fmt.Println("Ошибка открытия файла:", err)
return
}
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)
}
}
Обратите внимание на defer file.Close() — это гарантирует, что файл будет закрыт после завершения функции, даже если произойдёт ошибка.
Запись в файлы
Для записи данных в файл используйте os.WriteFile (создаёт/перезаписывает файл) или os.Create + Write (для более гибкой записи).
package main
import (
"fmt"
"os"
)
func main() {
// Простая запись строки в файл
text := "Привет, мир!\nВторая строка."
err := os.WriteFile("output.txt", []byte(text), 0644)
if err != nil {
fmt.Println("Ошибка записи:", err)
return
}
fmt.Println("Файл успешно записан")
}
Параметр 0644 — это права доступа в Unix-стиле (владелец может читать/писать, остальные — только читать).
Если нужно дописать данные в конец файла (append), используйте os.OpenFile с флагом os.O_APPEND:
package main
import (
"fmt"
"os"
)
func main() {
file, err := os.OpenFile("log.txt", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
fmt.Println("Ошибка открытия файла:", err)
return
}
defer file.Close()
_, err = file.WriteString("Новая запись в лог\n")
if err != nil {
fmt.Println("Ошибка записи:", err)
}
}
Работа с директориями
Go позволяет легко создавать, переименовывать и удалять папки. Рассмотрим базовые операции.
Создание директории
package main
import (
"fmt"
"os"
)
func main() {
// Создание одной директории
err := os.Mkdir("mydir", 0755)
if err != nil {
fmt.Println("Ошибка создания папки:", err)
return
}
// Создание вложенной структуры (как mkdir -p)
err = os.MkdirAll("parent/child/grandchild", 0755)
if err != nil {
fmt.Println("Ошибка создания вложенных папок:", err)
}
}
Чтение содержимого директории
Получить список файлов и папок внутри директории можно с помощью os.ReadDir:
package main
import (
"fmt"
"os"
)
func main() {
entries, err := os.ReadDir(".")
if err != nil {
fmt.Println("Ошибка чтения директории:", err)
return
}
for _, entry := range entries {
if entry.IsDir() {
fmt.Printf("[DIR] %s\n", entry.Name())
} else {
fmt.Printf("[FILE] %s\n", entry.Name())
}
}
}
Удаление файлов и папок
package main
import (
"fmt"
"os"
)
func main() {
// Удаление одного файла
err := os.Remove("temp.txt")
if err != nil {
fmt.Println("Ошибка удаления файла:", err)
}
// Рекурсивное удаление всей папки
err = os.RemoveAll("tempdir")
if err != nil {
fmt.Println("Ошибка удаления папки:", err)
}
}
Будьте осторожны с os.RemoveAll — он удаляет всё без возможности восстановления!
Проверка существования файла и получение информации
Перед работой с файлом часто нужно проверить, существует ли он. Для этого используйте os.Stat:
package main
import (
"fmt"
"os"
)
func main() {
fileInfo, err := os.Stat("example.txt")
if os.IsNotExist(err) {
fmt.Println("Файл не существует")