Menu

Чтение и запись файлов в Golang: os, bufio и папки

Как читать и записывать файлы в Go: os.ReadFile и os.WriteFile, построчное чтение через bufio.Scanner, дозапись через os.OpenFile, проверка существования файла и работа с папками.

На этой странице есть исполняемые редакторы: меняйте, запускайте и сразу видите результат.

Прочитать и записать файл целиком

os.WriteFile и os.ReadFile покрывают большинство задач. Они открывают, пишут или читают и закрывают файл одним вызовом.

Примеры на этой странице работают во временной папке из os.MkdirTemp и удаляют её через defer os.RemoveAll(dir), так что после себя ничего не оставляют. В вашем коде относительный путь вроде "config.json" разрешается относительно рабочей папки процесса, а это не обязательно папка исходного файла или бинарника.

os.WriteFile создаёт файл при необходимости и обрезает его, если он существует. Третий аргумент это Unix-права для вновь созданного файла: 0o644 означает, что владелец может читать и писать, а все остальные только читать. Для уже существующего файла он игнорируется, а umask процесса может убрать некоторые биты.

os.ReadFile читает всё в память. Это правильно для файлов конфигурации и небольшого ввода и неправильно для многогигабайтного лога.

Построчное чтение через bufio.Scanner

Для больших файлов или когда строки всё равно нужны, используйте bufio.Scanner. Он читает кусками и отдаёт по одной строке, без перевода строки.

Три детали:

  • Проверяйте sc.Err() после цикла. Scan возвращает false и в конце файла, и при ошибке, и различить их может только Err.
  • Ограничение строки в 64 КБ. По умолчанию одна строка длиннее 64 КБ останавливает сканер с bufio.Scanner: token too long. Для файлов с длинными строками (минифицированный JSON, некоторые логи) поднимите лимит до цикла: sc.Buffer(make([]byte, 1024*1024), 10*1024*1024).
  • Другие единицы. sc.Split(bufio.ScanWords) выдаёт слова; bufio.ScanRunes выдаёт символы.

Чтобы читать поток кусками фиксированного размера, а не строками, используйте f.Read(buf) в цикле или io.Copy в другой writer.

Запись: os.Create, os.OpenFile и дозапись

os.Create(name) открывает файл на запись, создавая или обрезая его. os.OpenFile даёт полный контроль через флаги:

ФлагЗначение
os.O_RDONLY, os.O_WRONLY, os.O_RDWRоткрыть на чтение, запись или и то, и другое (выберите одно)
os.O_CREATEсоздать файл, если его нет
os.O_TRUNCочистить файл при открытии
os.O_APPENDкаждая запись идёт в конец
os.O_EXCLвместе с O_CREATE: ошибка, если файл уже существует

os.Open(name) это OpenFile(name, O_RDONLY, 0). os.Create(name) это OpenFile(name, O_RDWR|O_CREATE|O_TRUNC, 0o666).

При записи важна ошибка от Close. Некоторые файловые системы сообщают о сбоях записи только при закрытии, поэтому один лишь defer f.Close() может скрыть потерю данных. Для файлов, в которые вы пишете, проверяйте Close явно, как это делает appendLine. Для файлов, которые вы только читаете, defer f.Close() достаточно.

fmt.Fprintln и остальные функции печати fmt принимают любой io.Writer, в том числе файл. bufio.Writer накапливает мелкие записи в памяти. Забытый w.Flush() это классический баг: программа завершается нормально, а последние несколько килобайт так и не попадают в файл.

Существует ли файл?

os.Exists в Go нет. Вызовите os.Stat и посмотрите на ошибку:

errors.Is(err, fs.ErrNotExist) это современная идиома. Она заменяет старый os.IsNotExist(err), который не видит сквозь обёрнутые ошибки.

Проверка перед открытием часто не нужна и порождает гонку: файл может появиться или исчезнуть между проверкой и открытием. Обычно файл просто открывают и обрабатывают fs.ErrNotExist от открытия. Чтобы создать файл, только если его ещё нет, используйте O_CREATE|O_EXCL: так проверка и создание становятся одним атомарным шагом.

Папки

ЗадачаФункция
создать одну папкуos.Mkdir(path, 0o755)
создать путь с родительскими папкамиos.MkdirAll(path, 0o755)
получить содержимое папкиos.ReadDir(path)
обойти деревоfilepath.WalkDir(root, fn)
удалить файл или пустую папкуos.Remove(path)
удалить деревоos.RemoveAll(path)
переименовать или переместитьos.Rename(old, new)
временный файл или папкаos.CreateTemp("", "prefix-*"), os.MkdirTemp("", "prefix")
соединить части путиfilepath.Join(a, b, c)

Для путей файловой системы используйте path/filepath: он берёт правильный для ОС разделитель (\ в Windows). Пакет path предназначен для путей через слэш, например URL.

В Go 1.24 также появился os.Root (os.OpenRoot(dir)), который открывает файлы только внутри одной папки и отвергает пути, выходящие из неё через .. или символические ссылки. Используйте его, когда имена файлов приходят от пользователей.

Частые ошибки

  • Ошибки не проверяются. Любой из этих вызовов может завершиться неудачей. Файл, который не открылся, равен nil, и каждый последующий Read, Write или Close на нём возвращает invalid argument, что прячет настоящую причину (файла не было или не хватило прав).
  • Забытый sc.Err() после цикла сканирования. Ошибка чтения выглядит как конец файла.
  • Забытый Flush у bufio.Writer. Конца файла не хватает.
  • Игнорирование ошибки от Close после записи. Ошибки записи могут проявиться только там.
  • defer f.Close() в цикле по множеству файлов. Файлы остаются открытыми до возврата из функции, и файловые дескрипторы могут закончиться. Вынесите тело в функцию, чтобы каждый файл закрывался на своей итерации.
  • Права, записанные в десятичном виде. 644 это не 0o644. Go читает 644 как десятичное число, то есть 0o1204, и выставляет странные биты.

Часто задаваемые вопросы

Как прочитать весь файл в строку в Go?

data, err := os.ReadFile("notes.txt") возвращает содержимое как []byte; преобразуйте через string(data). Функция сама открывает, читает и закрывает файл. Используйте её для файлов, которые спокойно помещаются в память; большие файлы читайте построчно через bufio.Scanner.

Как прочитать файл построчно в Go?

Откройте файл через os.Open, сделайте defer f.Close(), оберните его в bufio.NewScanner(f), пройдите циклом for sc.Scan() { line := sc.Text() } и после цикла проверьте sc.Err(). Строки длиннее 64 КБ ломают сканер с ошибкой token too long, если не увеличить его буфер через sc.Buffer.

Как дописать в файл в Go?

Откройте его через os.OpenFile(name, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644), запишите и проверьте ошибку от Close. O_CREATE создаёт файл, если его нет, а O_APPEND направляет каждую запись в конец.

Как проверить, существует ли файл, в Go?

Вызовите os.Stat(path) и проверьте ошибку через errors.Is(err, fs.ErrNotExist). Ошибка nil означает, что файл есть. Любая другая ошибка (например, нет прав) означает, что сказать нельзя, поэтому обрабатывайте её отдельно, а не считайте, что файла нет.

Coddy programming languages illustration

Учитесь программировать с Coddy

НАЧАТЬ