Работа с файловой системой в Go: практическое руководство
Go (Golang) — это современный компилируемый язык, который отлично подходит для создания серверных приложений, утилит и инструментов командной строки. Одной из ключевых задач в таких проектах является работа с файловой системой. В этой статье мы разберем практические примеры: от открытия и чтения файлов до рекурсивного обхода директорий. Все примеры написаны на чистом Go без сторонних библиотек.
Основы: открытие, чтение и запись файлов
Пакет os предоставляет функции для низкоуровневой работы с файлами. Для чтения всего содержимого файла в память используйте 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(string(data))
}
Для записи данных в файл существует функция os.WriteFile. Она создает файл, если его нет, или перезаписывает существующий.
package main
import (
"fmt"
"os"
)
func main() {
content := []byte("Hello, Go!\nВторая строка")
err := os.WriteFile("output.txt", content, 0644)
if err != nil {
fmt.Println("Ошибка записи:", err)
return
}
fmt.Println("Файл успешно записан")
}
Третий аргумент 0644 — это права доступа в Unix-стиле (владелец может читать и писать, остальные — только читать). В Windows этот параметр частично игнорируется.
Потоковое чтение и запись: работа с большими файлами
Для работы с большими файлами (более нескольких мегабайт) не стоит загружать их в память целиком. Используйте буферизированное чтение через bufio или пакет io.
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
file, err := os.Open("large_file.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)
}
}
Для потоковой записи используйте bufio.NewWriter:
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
file, err := os.Create("output_stream.txt")
if err != nil {
fmt.Println("Ошибка создания:", err)
return
}
defer file.Close()
writer := bufio.NewWriter(file)
for i := 0; i < 10; i++ {
_, err := writer.WriteString(fmt.Sprintf("Строка %d\n", i+1))
if err != nil {
fmt.Println("Ошибка записи:", err)
return
}
}
writer.Flush() // Важно: сбрасываем буфер
fmt.Println("Запись завершена")
}
Метод Flush() обязателен — без него данные могут остаться в буфере и не попасть в файл.
Работа с директориями: создание, удаление и обход
Создать новую директорию можно с помощью os.Mkdir (одна папка) или os.MkdirAll (рекурсивно, как mkdir -p).
package main
import (
"fmt"
"os"
)
func main() {
// Создать одну папку
err := os.Mkdir("new_folder", 0755)
if err != nil {
fmt.Println("Ошибка создания папки:", err)
}
// Создать вложенную структуру
err = os.MkdirAll("a/b/c/d", 0755)
if err != nil {
fmt.Println("Ошибка создания вложенных папок:", err)
} else {
fmt.Println("Папки созданы")
}
}
Удаление директории выполняется через os.Remove (только пустая) или os.RemoveAll (рекурсивно, будьте осторожны).
package main
import (
"fmt"
"os"
)
func main() {
err := os.Remove("temp_folder")
if err != nil {
fmt.Println("Ошибка удаления:", err)
}
// Рекурсивное удаление
err = os.RemoveAll("a")
if err != nil {
fmt.Println("Ошибка рекурсивного удаления:", err)
} else {
fmt.Println("Директория a и всё её содержимое удалено")
}
}
Для обхода содержимого директории используйте os.ReadDir (Go 1.16+) или ioutil.ReadDir (устарел).
package main
import (
"fmt"
"os"
)
func main() {
entries, err := os.ReadDir(".")
if err != nil {
fmt.Println("Ошибка чтения директории:", err)
return
}
for _, entry := range entries {
info, _ := entry.Info()
if entry.IsDir() {
fmt.Printf("[DIR] %s\n", entry.Name())
} else {
fmt.Printf("[FILE] %s (размер: %d байт)\n", entry.Name(), info.Size())
}
}
}
Для рекурсивного обхода всех поддиректорий используйте filepath.Walk:
package main
import (
"fmt"
"os"
"path/filepath"
)
func main() {
root := "."
err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() {
fmt.Printf("Папка: %s\n", path)
} else {
fmt.Printf("Файл: %s (%d байт)\n", path, info.Size())
}
return nil
})
if err != nil {
fmt.Println("Ошибка обхода:", err)
}
}
Проверка существования и получение информации о файлах
Перед работой с файлом часто нужно проверить, существует ли он. Используйте os.Stat:
package main
import (
"fmt"
"os"
)
func fileExists(path string) bool {
_, err := os.Stat(path)
return err == nil
}
func main() {
path := "example.txt"
if fileExists(path) {
info, _ := os.Stat(path)
fmt.Printf("Файл %s существует\n", path)
fmt.Printf("Размер: %d байт