Запись TXT и CSV из кода 1С 8.3: ЗаписьТекста, потоки, кодировка
Содержание
Чтобы программно создать текстовый файл в 1С 8.3, создайте объект Новый ЗаписьТекста(ИмяФайла, КодировкаТекста.UTF8), запишите строки методом ЗаписатьСтроку() и обязательно вызовите Закрыть(). Без Закрыть() часть данных останется в буфере, а файл будет заблокирован до конца сеанса.
Это работает на сервере и в тонком клиенте одинаково, но файл появится на том компьютере, где выполняется код. Именно на этом спотыкаются чаще всего: процедура отработала без ошибок, а файла у пользователя нет — он лежит на сервере 1С. Ниже код для типовых задач: простой TXT, выгрузка в CSV, файл без BOM, запись большого объёма и передача файла пользователю.
Базовые способы записать текст в файл из 1С
В платформе 8.3 есть три рабочих инструмента. Выбор зависит от объёма и от того, нужно ли редактировать текст перед записью.
| Способ | Когда подходит | Особенность |
|---|---|---|
ЗаписьТекста | Любой объём, построчная запись | Пишет сразу в файл, память почти не расходует |
ТекстовыйДокумент | Небольшой текст, который удобно собрать целиком | Держит весь текст в памяти, умеет показываться пользователю |
ДвоичныеДанные из строки | Готовая строка, нужен точный контроль байтов | Удобно для UTF-8 без BOM и передачи на клиент |
ЗаписьТекста — основной вариант
ИмяФайла = "C:\Обмен\выгрузка.txt";
Запись = Новый ЗаписьТекста(ИмяФайла, КодировкаТекста.UTF8);
Запись.ЗаписатьСтроку("Первая строка");
Запись.ЗаписатьСтроку("Вторая строка");
Запись.Записать("Текст без перевода строки");
Запись.Закрыть();
Разница между методами простая:
- 📝
ЗаписатьСтроку()добавляет текст и перевод строки в конце; - ✏️
Записать()добавляет только текст, следующая запись продолжит ту же строку.
Полная форма конструктора: Новый ЗаписьТекста(ИмяФайла, Кодировка, РазделительСтрок, Дописывать, РазделительСтрокВФайле). Четвёртый параметр Дописывать = Истина открывает существующий файл и пишет в конец. Так ведут журналы обмена:
Журнал = Новый ЗаписьТекста(ИмяФайлаЖурнала, КодировкаТекста.UTF8, , Истина);
Журнал.ЗаписатьСтроку(Формат(ТекущаяДатаСеанса(), "ДФ='dd.MM.yyyy HH:mm:ss'") + " Обмен завершён");
Журнал.Закрыть();
Если файла нет, он будет создан. Если есть и Дописывать не указан — содержимое перезапишется без предупреждения.
ТекстовыйДокумент — когда текст удобнее собрать целиком
Текст = Новый ТекстовыйДокумент;
Текст.ДобавитьСтроку("Отчёт по остаткам");
Текст.ДобавитьСтроку("Склад: Основной");
Текст.Записать(ИмяФайла, КодировкаТекста.UTF8);
Объект хранит весь текст в памяти. На паре тысяч строк это незаметно, на сотнях тысяч — уже ощутимо. Зато его можно показать пользователю методом Показать() до сохранения или прочитать файл обратно через Прочитать().
Если строки уже собраны в массив, СтрСоединить(Массив, Символы.ПС) даёт готовый текст одной операцией. Это быстрее, чем склеивать строку через + в цикле.
Где появится файл: клиент или сервер
Код, помеченный &НаСервере, создаёт файл на компьютере, где работает сервер 1С. Код &НаКлиенте в тонком клиенте — на компьютере пользователя. В файловой базе клиент и сервер часто одна машина, поэтому разница всплывает только при переходе на клиент-серверный вариант.
Правильная схема для «сформировать файл и отдать пользователю»:
- На сервере собрать текст и получить из него двоичные данные.
- Поместить их во временное хранилище:
ПоместитьВоВременноеХранилище(Данные, УникальныйИдентификатор). - На клиенте получить файл: в 8.3.18 и новее —
ПолучитьФайлССервераАсинх(), в более ранних —НачатьПолучениеФайлаССервера().
&НаСервере
Функция ПодготовитьФайл()
ТекстФайла = "Строка 1" + Символы.ПС + "Строка 2";
Данные = ПолучитьДвоичныеДанныеИзСтроки(ТекстФайла, КодировкаТекста.UTF8);
Возврат ПоместитьВоВременноеХранилище(Данные, УникальныйИдентификатор);
КонецФункции
&НаКлиенте
Асинх Процедура Выгрузить(Команда)
Адрес = ПодготовитьФайл();
Ждать ПолучитьФайлССервераАсинх(Адрес, "выгрузка.txt");
КонецПроцедуры
Пользователь увидит стандартный диалог сохранения или файл попадёт в загрузки браузера в веб-клиенте. В веб-клиенте ЗаписьТекста недоступна, поэтому этот путь там единственный.
Если на сервере нужен промежуточный файл, берите имя из ПолучитьИмяВременногоФайла("txt") и удаляйте его после использования через УдалитьФайлы(ИмяФайла). Временный каталог сервера не чистится сам, и за год там скапливаются гигабайты.
Файловые потоки и буферизация при записи
Начиная с 8.3.9 в платформе есть потоки. ЗаписьТекста умеет писать не только по имени файла, но и в поток. Это нужно, когда один и тот же код должен писать то в файл, то в память, или когда файл открывают с особыми режимами доступа.
Поток = ФайловыеПотоки.ОткрытьДляЗаписи(ИмяФайла);
Запись = Новый ЗаписьТекста(Поток, КодировкаТекста.UTF8);
Для Каждого Строка Из ТаблицаДанных Цикл
Запись.ЗаписатьСтроку(Строка.Наименование);
КонецЦикла;
Запись.Закрыть();
Поток.Закрыть();
Закрывайте в таком порядке: сначала объект записи, потом поток.
Что важно знать про буфер:
- 🧱
ЗаписьТекстакопит данные в памяти и сбрасывает их на диск порциями. Пока объект не закрыт, другой процесс может увидеть файл неполным. - 🔄 У потока есть метод
СброситьБуферы(). Он принудительно отправляет накопленное на диск, не закрывая файл. Полезно для долгих журналов, которые читает другая программа. - 🔒 Пока файл открыт на запись, внешняя программа его, как правило, не откроет или откроет только на чтение.
Если между созданием объекта и Закрыть() возникнет исключение, файл останется открытым до конца сеанса или сборки мусора. Закрывайте его в ветке Исключение (пример ниже, в разделе про ошибки).
Кодировка UTF-8, ANSI и символ BOM
Кодировка задаётся вторым параметром. Основные варианты:
| Значение | Что получится | Кому нужно |
|---|---|---|
КодировкаТекста.UTF8 | UTF-8 с BOM в начале файла | Excel, Блокнот, большинство обменов с Windows-программами |
КодировкаТекста.ANSI | Windows-1251 на русской Windows | Старые банк-клиенты, DOS-подобные форматы |
КодировкаТекста.OEM | Кодовая страница 866 | Пакетные файлы .bat с кириллицей |
КодировкаТекста.UTF16 | UTF-16 | Редко, отдельные внешние системы |
Строка, например "windows-1251" | Явное имя кодировки | Когда нужна конкретная кодовая страница независимо от ОС |
Без второго параметра платформа выберет кодировку сама, и на разных серверах результат может отличаться. Указывайте кодировку всегда.
Что такое BOM и когда он мешает
BOM — три служебных байта EF BB BF в начале UTF-8 файла. Excel по ним понимает, что CSV в UTF-8, и показывает кириллицу без кракозябр. А вот PHP-скрипты, JSON-парсеры, некоторые банковские и маркетплейс-загрузчики считают BOM мусором: первая строка ломается или первый заголовок колонки не распознаётся.
ЗаписьТекста с КодировкаТекста.UTF8 пишет BOM. Получить файл без него можно двумя способами.
Способ 1. Через двоичные данные. Третий параметр ПолучитьДвоичныеДанныеИзСтроки отвечает за BOM, по умолчанию он Ложь:
Данные = ПолучитьДвоичныеДанныеИзСтроки(ТекстФайла, КодировкаТекста.UTF8, Ложь);
Данные.Записать(ИмяФайла);
Подходит, когда текст уже собран в строку.
Способ 2. Режим дописывания. При Дописывать = Истина BOM не добавляется. Создаём пустой файл и дописываем в него:
Пустой = Новый ЗаписьТекста(ИмяФайла, КодировкаТекста.ANSI);
Пустой.Закрыть();
Запись = Новый ЗаписьТекста(ИмяФайла, КодировкаТекста.UTF8, , Истина);
Запись.ЗаписатьСтроку("id;name");
Запись.Закрыть();
Способ подходит для больших файлов, где строку целиком в памяти держать не хочется.
Проверить, есть ли BOM, проще всего в Notepad++: в нижней строке состояния будет «UTF-8-BOM» или просто «UTF-8».
Переводы строк: CRLF или LF
На Windows ЗаписьТекста по умолчанию пишет переводы строк как CR+LF. Для Linux-систем и многих API нужен только LF. Передайте его пятым параметром:
Запись = Новый ЗаписьТекста(ИмяФайла, КодировкаТекста.UTF8, , , Символы.ПС);
Выгрузка таблицы в CSV: разделители и экранирование
Самая частая практическая задача — выгрузить таблицу значений или результат запроса в CSV. Здесь три подводных камня: разделитель, кавычки внутри значений и формат чисел и дат.
Какой разделитель выбрать
- ⌨️
;— стандарт для русского Excel, он открывает такой файл по двойному щелчку сразу по колонкам; - ➡️
Символы.Таб— безопасный вариант, если в данных встречаются точки с запятой (адреса, комментарии); - 🔤 запятая — для англоязычных систем, но конфликтует с десятичной запятой в числах.
Экранирование значений
Если значение содержит разделитель, кавычку или перевод строки, его берут в двойные кавычки, а кавычки внутри удваивают. Вынесите это в функцию:
Функция ЗначениеCSV(Значение, Разделитель = ";")
Если ТипЗнч(Значение) = Тип("Число") Тогда
Возврат Формат(Значение, "ЧРД=.; ЧГ=0; ЧН=0");
ИначеЕсли ТипЗнч(Значение) = Тип("Дата") Тогда
Возврат Формат(Значение, "ДФ=dd.MM.yyyy");
КонецЕсли;
Стр = Строка(Значение);
Если СтрНайти(Стр, Разделитель) > 0
Или СтрНайти(Стр, """") > 0
Или СтрНайти(Стр, Символы.ПС) > 0 Тогда
Стр = """" + СтрЗаменить(Стр, """", """""") + """";
КонецЕсли;
Возврат Стр;
КонецФункции
Формат ЧГ=0 убирает пробелы между разрядами: без него 1500000 превратится в 1 500 000, и внешняя система не распознает число. ЧН=0 выводит ноль вместо пустой строки.
Сборка файла
Запись = Новый ЗаписьТекста(ИмяФайла, КодировкаТекста.UTF8);
ЗаголовокМассив = Новый Массив;
Для Каждого Колонка Из Таблица.Колонки Цикл
ЗаголовокМассив.Добавить(ЗначениеCSV(Колонка.Имя));
КонецЦикла;
Запись.ЗаписатьСтроку(СтрСоединить(ЗаголовокМассив, ";"));
Для Каждого СтрокаТаблицы Из Таблица Цикл
Поля = Новый Массив;
Для Каждого Колонка Из Таблица.Колонки Цикл
Поля.Добавить(ЗначениеCSV(СтрокаТаблицы[Колонка.Имя]));
КонецЦикла;
Запись.ЗаписатьСтроку(СтрСоединить(Поля, ";"));
КонецЦикла;
Запись.Закрыть();
Ссылочные значения (контрагент, номенклатура) функция превратит в представление через Строка(). Если внешней системе нужен код или ИНН, выберите их отдельными полями в запросе, а не берите представление.
Если CSV будут открывать в Excel, оставьте BOM. Без него русский Excel откроет UTF-8 файл в ANSI, и кириллица превратится в набор символов вида Рџ.
Ошибки записи и права доступа к каталогу
Запись в файл — операция с внешним миром, она падает по причинам, которые от кода не зависят. Оборачивайте её в Попытка:
Запись = Неопределено;
Попытка
Запись = Новый ЗаписьТекста(ИмяФайла, КодировкаТекста.UTF8);
Запись.ЗаписатьСтроку(Текст);
Запись.Закрыть();
Исключение
Если Запись <> Неопределено Тогда
Запись.Закрыть();
КонецЕсли;
ЗаписьЖурналаРегистрации("Выгрузка.ЗаписьФайла", УровеньЖурналаРегистрации.Ошибка, , ,
ОписаниеОшибки());
ВызватьИсключение;
КонецПопытки;
Перед записью полезно проверить каталог и создать его при необходимости:
Каталог = Новый Файл("C:\Обмен\Выгрузки");
Если Не Каталог.Существует() Тогда
СоздатьКаталог(Каталог.ПолноеИмя);
КонецЕсли;
Не выдавайте учётной записи сервера 1С права администратора Windows ради одной выгрузки. Достаточно прав на запись в конкретную папку.
Асинхронная запись и скорость на больших объёмах
На клиенте синхронные файловые операции блокируют интерфейс, а в веб-клиенте и при включённом режиме запрета модальности часть из них недоступна. Для ТекстовыйДокумент есть асинхронная запись: в 8.3.18 и новее — ЗаписатьАсинх() с Ждать, раньше — НачатьЗапись() с описанием оповещения.
&НаКлиенте
Асинх Процедура СохранитьТекст(Команда)
Текст = Новый ТекстовыйДокумент;
Текст.УстановитьТекст(ТекстДляСохранения);
Ждать Текст.ЗаписатьАсинх(ИмяФайла, КодировкаТекста.UTF8);
КонецПроцедуры
Для серверной выгрузки асинхронность не нужна: там важнее объём памяти и время. Что реально ускоряет запись:
- Пишите построчно через
ЗаписьТекста, а не копите всё в одну строку. Склейка строки через+в цикле на 100 000 строк заметно замедляется, потому что каждый раз создаётся новая строка. - Собирайте поля строки в массив и соединяйте через
СтрСоединить()— это быстрее серии конкатенаций. - Обходите выборку запроса (
Выборка.Следующий()), а не выгружайте результат в таблицу значений, если таблица нужна только для записи. - Не открывайте файл заново на каждой строке. Открыть, записать всё, закрыть один раз — разница с вариантом «дописывать по строке» на больших объёмах кратная.
- Выносите тяжёлую выгрузку в фоновое задание через
ФоновыеЗадания.Выполнить(). Пользователь продолжит работать, а файл сформируется на сервере.
| Объём | Рекомендуемый способ |
|---|---|
| До нескольких тысяч строк | ТекстовыйДокумент или строка → ПолучитьДвоичныеДанныеИзСтроки |
| Десятки и сотни тысяч строк | ЗаписьТекста с обходом выборки |
| Долгая выгрузка, которую ждёт пользователь | Фоновое задание + временное хранилище для результата |