Запись TXT и CSV из кода 1С 8.3: ЗаписьТекста, потоки, кодировка

· 10 мин

Содержание

Чтобы программно создать текстовый файл в 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С. Код &НаКлиенте в тонком клиенте — на компьютере пользователя. В файловой базе клиент и сервер часто одна машина, поэтому разница всплывает только при переходе на клиент-серверный вариант.

Правильная схема для «сформировать файл и отдать пользователю»:

  1. На сервере собрать текст и получить из него двоичные данные.
  2. Поместить их во временное хранилище: ПоместитьВоВременноеХранилище(Данные, УникальныйИдентификатор).
  3. На клиенте получить файл: в 8.3.18 и новее — ПолучитьФайлССервераАсинх(), в более ранних — НачатьПолучениеФайлаССервера().
&НаСервере
Функция ПодготовитьФайл()
    ТекстФайла = "Строка 1" + Символы.ПС + "Строка 2";
    Данные = ПолучитьДвоичныеДанныеИзСтроки(ТекстФайла, КодировкаТекста.UTF8);
    Возврат ПоместитьВоВременноеХранилище(Данные, УникальныйИдентификатор);
КонецФункции

&НаКлиенте
Асинх Процедура Выгрузить(Команда)
    Адрес = ПодготовитьФайл();
    Ждать ПолучитьФайлССервераАсинх(Адрес, "выгрузка.txt");
КонецПроцедуры

Пользователь увидит стандартный диалог сохранения или файл попадёт в загрузки браузера в веб-клиенте. В веб-клиенте ЗаписьТекста недоступна, поэтому этот путь там единственный.

Если на сервере нужен промежуточный файл, берите имя из ПолучитьИмяВременногоФайла("txt") и удаляйте его после использования через УдалитьФайлы(ИмяФайла). Временный каталог сервера не чистится сам, и за год там скапливаются гигабайты.

Файловые потоки и буферизация при записи

Начиная с 8.3.9 в платформе есть потоки. ЗаписьТекста умеет писать не только по имени файла, но и в поток. Это нужно, когда один и тот же код должен писать то в файл, то в память, или когда файл открывают с особыми режимами доступа.

Поток = ФайловыеПотоки.ОткрытьДляЗаписи(ИмяФайла);
Запись = Новый ЗаписьТекста(Поток, КодировкаТекста.UTF8);

Для Каждого Строка Из ТаблицаДанных Цикл
    Запись.ЗаписатьСтроку(Строка.Наименование);
КонецЦикла;

Запись.Закрыть();
Поток.Закрыть();

Закрывайте в таком порядке: сначала объект записи, потом поток.

Что важно знать про буфер:

  • 🧱 ЗаписьТекста копит данные в памяти и сбрасывает их на диск порциями. Пока объект не закрыт, другой процесс может увидеть файл неполным.
  • 🔄 У потока есть метод СброситьБуферы(). Он принудительно отправляет накопленное на диск, не закрывая файл. Полезно для долгих журналов, которые читает другая программа.
  • 🔒 Пока файл открыт на запись, внешняя программа его, как правило, не откроет или откроет только на чтение.

Если между созданием объекта и Закрыть() возникнет исключение, файл останется открытым до конца сеанса или сборки мусора. Закрывайте его в ветке Исключение (пример ниже, в разделе про ошибки).

Кодировка UTF-8, ANSI и символ BOM

Кодировка задаётся вторым параметром. Основные варианты:

ЗначениеЧто получитсяКому нужно
КодировкаТекста.UTF8UTF-8 с BOM в начале файлаExcel, Блокнот, большинство обменов с Windows-программами
КодировкаТекста.ANSIWindows-1251 на русской WindowsСтарые банк-клиенты, DOS-подобные форматы
КодировкаТекста.OEMКодовая страница 866Пакетные файлы .bat с кириллицей
КодировкаТекста.UTF16UTF-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);
КонецПроцедуры

Для серверной выгрузки асинхронность не нужна: там важнее объём памяти и время. Что реально ускоряет запись:

  1. Пишите построчно через ЗаписьТекста, а не копите всё в одну строку. Склейка строки через + в цикле на 100 000 строк заметно замедляется, потому что каждый раз создаётся новая строка.
  2. Собирайте поля строки в массив и соединяйте через СтрСоединить() — это быстрее серии конкатенаций.
  3. Обходите выборку запроса (Выборка.Следующий()), а не выгружайте результат в таблицу значений, если таблица нужна только для записи.
  4. Не открывайте файл заново на каждой строке. Открыть, записать всё, закрыть один раз — разница с вариантом «дописывать по строке» на больших объёмах кратная.
  5. Выносите тяжёлую выгрузку в фоновое задание через ФоновыеЗадания.Выполнить(). Пользователь продолжит работать, а файл сформируется на сервере.
ОбъёмРекомендуемый способ
До нескольких тысяч строкТекстовыйДокумент или строка → ПолучитьДвоичныеДанныеИзСтроки
Десятки и сотни тысяч строкЗаписьТекста с обходом выборки
Долгая выгрузка, которую ждёт пользовательФоновое задание + временное хранилище для результата

Частые вопросы

Можно ли создать текстовый файл в веб-клиенте 1С?
Напрямую через ЗаписьТекста — нет, объект в веб-клиенте недоступен. Сформируйте данные на сервере и передайте пользователю через временное хранилище и получение файла с сервера.
Как дописать строку в существующий файл, не стирая его?
Укажите четвёртый параметр конструктора: Новый ЗаписьТекста(ИмяФайла, КодировкаТекста.UTF8, , Истина). Если файла нет, он будет создан.
Чем КодировкаТекста.ANSI отличается от строки windows-1251?
ANSI берёт кодовую страницу операционной системы, на которой выполняется код. На русской Windows это 1251, на сервере с другой локалью или на Linux результат может быть иным. Явное имя кодировки даёт предсказуемый результат.
Почему после выгрузки в CSV у чисел пропадают копейки или появляются пробелы?
Число превратилось в строку по формату по умолчанию: с пробелами между разрядами и десятичной запятой. Используйте Формат с параметрами ЧРД, ЧГ=0 и задайте нужный разделитель дробной части явно.
Как записать файл в кодировке для bat-скрипта с кириллицей?
Используйте КодировкаТекста.OEM: консоль Windows по умолчанию работает в кодовой странице 866, и русские пути в такой кодировке читаются корректно.