Прочитать и записать файл целиком
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 означает, что файл есть. Любая другая ошибка (например, нет прав) означает, что сказать нельзя, поэтому обрабатывайте её отдельно, а не считайте, что файла нет.