Запись файла на диск из кода 1С: текст, двоичные данные, потоки
Содержание
Чтобы программно создать файл в 1С, достаточно трёх строк: Текст = Новый ТекстовыйДокумент; Текст.ДобавитьСтроку("Привет"); Текст.Записать("C:\Temp\test.txt", КодировкаТекста.UTF8);. Так пишут небольшие текстовые файлы. Для больших выгрузок берут ЗаписьТекста — она пишет построчно и не держит весь текст в памяти. Для картинок, архивов и любых готовых байтов нужны ДвоичныеДанные или файловые потоки.
Выбор зависит от того, что пишем и где выполняется код. Код на сервере создаёт файл на диске сервера, код на клиенте — на компьютере пользователя. Эту разницу чаще всего упускают начинающие разработчики, и из-за неё «файл не появился». Все примеры ниже — для платформы 8.3 и управляемых форм.
Какой объект выбрать для записи файла
У платформы несколько способов создать файл. Они не взаимозаменяемы:
| Объект | Что пишет | Когда брать | Ограничения |
|---|---|---|---|
ТекстовыйДокумент | Текст целиком | Небольшие файлы, логи, CSV до нескольких мегабайт | Весь текст лежит в памяти до записи |
ЗаписьТекста | Текст построчно | Большие выгрузки, дописывание в конец файла | Нужно явно закрыть |
ДвоичныеДанные | Готовые байты | Файл из Base64, из хранилища, из HTTP-ответа | Тоже целиком в памяти |
ФайловыйПоток и ЗаписьДанных | Байты и типизированные значения | Двоичные форматы, большие объёмы, запись частями | Платформа 8.3.9 и выше |
ЗаписьXML, ЗаписьJSON | Структурированные данные | Обмены, интеграции | Только свой формат |
ТабличныйДокумент.Записать | Печатные формы | Выгрузка в XLSX, PDF, MXL | Только табличный документ |
Если сомневаетесь, начните с ЗаписьТекста. Она подходит почти под любой текстовый файл и не упирается в память.
Ориентир по объёму: выгрузка на 10–20 тысяч строк спокойно проходит через ТекстовыйДокумент. Когда строк сотни тысяч, а файл весит десятки мегабайт, переходите на построчную запись или поток. Иначе рабочий процесс сервера держит в памяти весь текст сразу, и при нескольких параллельных выгрузках это уже заметно на других пользователях.
Создание текстового файла: ТекстовыйДокумент и ЗаписьТекста
ТекстовыйДокумент — когда текста немного
Объект собирает текст в памяти, а затем одним вызовом сохраняет его на диск.
&НаСервере
Процедура СоздатьФайлНаСервере(ИмяФайла)
Текст = Новый ТекстовыйДокумент;
Текст.ДобавитьСтроку("Код;Наименование;Цена");
Для Каждого Строка Из Товары Цикл
Текст.ДобавитьСтроку(СтрШаблон("%1;%2;%3",
Строка.Код, Строка.Наименование, Формат(Строка.Цена, "ЧРД=.; ЧГ=0")));
КонецЦикла;
Текст.Записать(ИмяФайла, КодировкаТекста.UTF8);
КонецПроцедуры
Удобно, что можно вставлять и заменять строки в любом месте до записи: ВставитьСтроку, ЗаменитьСтроку, УдалитьСтроку. Минус — на выгрузке в сотни тысяч строк память сервера растёт заметно.
ЗаписьТекста — построчная запись больших файлов
ЗаписьТекста открывает файл и сразу пишет в него каждую строку. Памяти почти не тратит.
Запись = Новый ЗаписьТекста(ИмяФайла, КодировкаТекста.UTF8);
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
Запись.ЗаписатьСтроку(Выборка.Наименование);
КонецЦикла;
Запись.Закрыть();
Два метода, которые путают:
- 📝
ЗаписатьСтроку— добавляет текст и перевод строки; - ✏️
Записать— добавляет текст без перевода строки.
Чтобы дописать в существующий файл, а не перезаписать его, передайте четвёртым параметром конструктора Истина (параметр «Дописывать»):
Лог = Новый ЗаписьТекста(ИмяФайла, КодировкаТекста.UTF8, , Истина);
Лог.ЗаписатьСтроку(Формат(ТекущаяДата(), "ДФ='dd.MM.yyyy HH:mm:ss'") + " Выгрузка завершена");
Лог.Закрыть();
Всегда вызывайте Закрыть(). Пока объект не закрыт, файл занят процессом 1С. Другая программа не сможет его открыть, а последние строки могут остаться в буфере и не попасть на диск.
Двоичные данные и потоки: файлы любого формата
Текстовые объекты не подходят для картинок, PDF, ZIP и других двоичных форматов. Здесь работают ДвоичныеДанные и потоки.
Файл из ДвоичныхДанных
Самый короткий путь — получить объект ДвоичныеДанные и вызвать у него Записать:
// Файл пришёл строкой Base64, например из веб-сервиса
ДД = Base64Значение(СтрокаBase64);
ДД.Записать("C:\Exchange\scan.pdf");
// Файл лежит во временном хранилище
ДД = ПолучитьИзВременногоХранилища(Адрес);
ДД.Записать(ИмяФайла);
// Строка превращается в байты с нужной кодировкой
ДД = ПолучитьДвоичныеДанныеИзСтроки("Текст", КодировкаТекста.UTF8, Ложь);
ДД.Записать(ИмяФайла);
Последний вариант полезен, когда нужен точный контроль над байтами текстового файла. Третий параметр ПолучитьДвоичныеДанныеИзСтроки отвечает за метку BOM: при Ложь её не будет.
Запись через файловый поток
Поток нужен, когда файл собирается частями или он слишком велик, чтобы держать его в памяти целиком. Объекты потоков есть в платформе с версии 8.3.9.
Поток = Новый ФайловыйПоток(ИмяФайла, РежимОткрытияФайла.Создать, ДоступКФайлу.Запись);
Попытка
Для Каждого Часть Из ЧастиФайла Цикл // массив ДвоичныхДанных
Буфер = ПолучитьБуферДвоичныхДанныхИзДвоичныхДанных(Часть);
Поток.Записать(Буфер, 0, Буфер.Размер);
КонецЦикла;
Поток.Закрыть();
Исключение
Поток.Закрыть();
ВызватьИсключение;
КонецПопытки;
Если формат требует записывать числа фиксированной длины (заголовки двоичных файлов, протоколы оборудования), поверх потока создают ЗаписьДанных. У неё есть методы для байта, целых чисел разной разрядности и строк, а порядок байтов задаётся в конструкторе.
Режим РежимОткрытияФайла.Создать создаёт новый файл, а существующий перезаписывает. Для дописывания в конец есть отдельный режим Дописать.
Кодировка и перевод строки: как не получить «кракозябры»
Файл может записаться без ошибок, но принимающая программа покажет вместо букв мусор. Почти всегда виноваты кодировка или BOM.
Варианты кодировки задают системным перечислением или строкой:
- 🌐
КодировкаТекста.UTF8— стандарт для обменов, сайтов, JSON; - 🪟
КодировкаТекста.ANSI— на русской Windows это windows-1251; её ждут старые программы и банк-клиенты; - 💾
КодировкаТекста.OEM— cp866, для консольных утилит и bat-файлов с кириллицей; - 🔤 строка вида
"windows-1251"или"koi8-r"— когда нужна конкретная кодовая страница независимо от настроек сервера.
ANSI и OEM зависят от локали операционной системы. На Linux-сервере ANSI может оказаться не windows-1251. Если получателю нужна именно 1251, передавайте строку "windows-1251".
Про BOM. Объекты ТекстовыйДокумент и ЗаписьТекста при КодировкаТекста.UTF8 записывают в начало файла метку BOM — три служебных байта. Excel ей рад, а многие веб-сервисы и парсеры CSV спотыкаются: первая колонка получает «невидимый» символ в названии. Нужен UTF-8 без BOM — соберите текст в строку и запишите через ПолучитьДвоичныеДанныеИзСтроки(Текст, КодировкаТекста.UTF8, Ложь).
Про перевод строки. В Windows строки разделяют два символа (Символы.ВК + Символы.ПС), в Linux и в большинстве API — один Символы.ПС. Разделитель задают параметром:
// ТекстовыйДокумент: третий параметр — разделитель строк в файле
Текст.Записать(ИмяФайла, КодировкаТекста.UTF8, Символы.ПС);
// ЗаписьТекста: третий параметр — разделитель в тексте, пятый — в файле
Запись = Новый ЗаписьТекста(ИмяФайла, КодировкаТекста.UTF8, Символы.ПС, Ложь, Символы.ПС);
Проверка файла перед записью и защита от перезаписи
ТекстовыйДокумент.Записать и ДвоичныеДанные.Записать молча заменяют существующий файл. Если старые данные важны, проверьте его заранее через объект Файл:
Файл = Новый Файл(ИмяФайла);
Если Файл.Существует() Тогда
Если Файл.ЭтоКаталог() Тогда
ВызватьИсключение "По пути " + ИмяФайла + " находится каталог, а не файл";
КонецЕсли;
ПереместитьФайл(ИмяФайла, ИмяФайла + ".bak"); // сохранили прежнюю версию
КонецЕсли;
Ещё несколько приёмов:
- Каталог. Запись в несуществующую папку завершится ошибкой. Перед записью проверьте каталог тем же
Новый Файл(Путь).Существует()и при необходимости создайте его черезСоздатьКаталог(Путь). - Имя без коллизий. Для промежуточных файлов берите
ПолучитьИмяВременногоФайла("csv")— платформа вернёт уникальное имя во временном каталоге. Не забудьте потом удалить файл черезУдалитьФайлы(ИмяФайла). - Сборка пути. Не склеивайте путь с жёстким
"\". Разделитель в Windows и Linux разный, его возвращаетПолучитьРазделительПути(). - Атомарная замена. Если файл забирает другая программа, пишите сначала во временный файл, а потом переименуйте его через
ПереместитьФайл. Тогда внешняя система не прочитает файл, записанный наполовину.
Где окажется файл: клиент, сервер, файловый режим
Место записи файла определяет не путь, а контекст выполнения кода.
| Режим работы | Код &НаСервере пишет на | Код &НаКлиенте пишет на |
|---|---|---|
| Файловая база, тонкий или толстый клиент | Компьютер пользователя (серверная часть работает в процессе клиента) | Компьютер пользователя |
| Клиент-серверная база | Сервер 1С, где работает рабочий процесс rphost | Компьютер пользователя |
| Веб-клиент | Машина, где опубликована база или стоит сервер 1С | Нужно расширение для работы с файлами в браузере |
Права учётной записи на сервере
В клиент-серверном варианте серверный код пишет от имени учётной записи, под которой запущена служба сервера 1С (по умолчанию USR1CV8). Права пользователя Windows, который сидит за клиентом, роли не играют. Поэтому код Текст.Записать("C:\Users\Иванов\Desktop\отчёт.txt") на сервере обычно падает с отказом в доступе: такого пути на сервере может не быть вовсе, а у службы нет прав на чужой профиль.
Для сетевых папок указывайте UNC-путь вида \\fileserver\exchange\, а не букву подключённого диска. Буквы дисков существуют только в сеансе конкретного пользователя, служба их не видит. Учётной записи службы дайте права на запись в эту папку.
Как отдать файл пользователю
Типовая схема: файл формируют на сервере, кладут во временное хранилище и передают на клиент.
&НаСервере
Функция СформироватьФайлНаСервере()
ИмяВременного = ПолучитьИмяВременногоФайла("txt");
Текст = Новый ТекстовыйДокумент;
Текст.ДобавитьСтроку("Данные выгрузки");
Текст.Записать(ИмяВременного, КодировкаТекста.UTF8);
Адрес = ПоместитьВоВременноеХранилище(Новый ДвоичныеДанные(ИмяВременного), УникальныйИдентификатор);
УдалитьФайлы(ИмяВременного);
Возврат Адрес;
КонецФункции
&НаКлиенте
Асинх Процедура Выгрузить(Команда)
Адрес = СформироватьФайлНаСервере();
Ждать ПолучитьФайлССервераАсинх(Адрес, "выгрузка.txt");
КонецПроцедуры
Асинх, Ждать и ПолучитьФайлССервераАсинх доступны с платформы 8.3.18. На более ранних релизах используют НачатьПолучениеФайлов с описанием оповещения.
Синхронные вызовы на клиенте
Если в свойствах конфигурации режим использования синхронных вызовов установлен в «Не использовать», клиентский код не может вызвать ТекстовыйДокумент.Записать. Платформа выдаст ошибку о запрете синхронных методов. На клиенте применяйте асинхронные аналоги, например ТекстовыйДокумент.ЗаписатьАсинх, или переносите запись на сервер.
Расширения конфигурации и внешние обработки в безопасном режиме не получают доступ к файловой системе. Если код из расширения не пишет файл, проверьте флаг безопасного режима и профиль безопасности, назначенный информационной базе.
Ошибки при записи файла и журнал регистрации
Запись на диск — внешняя операция: диск переполнен, файл открыт в Excel, у службы нет прав. Оборачивайте её в Попытка и фиксируйте причину в журнале регистрации, иначе разбирать сбой регламентного задания будет нечем.
Запись = Неопределено;
Попытка
Запись = Новый ЗаписьТекста(ИмяФайла, КодировкаТекста.UTF8);
Запись.ЗаписатьСтроку(ДанныеВыгрузки);
Запись.Закрыть();
Исключение
Если Запись <> Неопределено Тогда
Попытка
Запись.Закрыть();
Исключение
// файл уже недоступен, закрывать нечего
КонецПопытки;
КонецЕсли;
ТекстОшибки = ПодробноеПредставлениеОшибки(ИнформацияОбОшибке());
ЗаписьЖурналаРегистрации("Выгрузка.ЗаписьФайла",
УровеньЖурналаРегистрации.Ошибка, , ИмяФайла, ТекстОшибки);
ВызватьИсключение;
КонецПопытки;
Что здесь важно:
- 🔒 файл закрывается и при ошибке — иначе он остаётся занятым до конца сеанса;
- 🧾 в журнал попадает имя файла в поле «Данные» — по нему удобно фильтровать события;
- ↩️
ВызватьИсключениебез параметров пробрасывает исходную ошибку дальше, и вызывающий код узнаёт о сбое.
Функция ПодробноеПредставлениеОшибки появилась в 8.3.17. На старых релизах используйте ОписаниеОшибки().
Найти записанные события можно в режиме «1С:Предприятие»: в разделе Администрирование откройте журнал регистрации и отберите события по имени Выгрузка.ЗаписьФайла.
Частые вопросы
Как записать файл сразу в Excel, а не в CSV?
ТабличныйДокумент.Записать(ИмяФайла, ТипФайлаТабличногоДокумента.XLSX). Excel на сервере для этого не нужен.Можно ли создать файл в каталоге информационной базы?
Как создать пустой файл нужного имени?
ТекстовыйДокумент или откройте ЗаписьТекста и сразу закройте её. Учтите, что в UTF-8 с BOM файл получит три служебных байта.Как выгрузить XML или JSON в файл?
ЗаписьXML и ЗаписьJSON есть метод ОткрытьФайл(ИмяФайла). После записи данных вызовите Закрыть(), как и для текста.Почему регламентное задание не видит подключённый сетевой диск?
\\сервер\папка.