Обходим папку и читаем файлы встроенным языком 1С 8.3

· 9 мин

Содержание

Чтобы прочитать файлы из каталога в 1С, получите их список функцией НайтиФайлы(Каталог, "*.txt"), а затем откройте каждый файл объектом ЧтениеТекста (текст) или через ФайловыеПотоки.ОткрытьДляЧтения() (двоичные данные). Это три строки кода, и в простом случае больше ничего не нужно.

Сложности начинаются позже: файл в кодировке 1251 читается кракозябрами, Excel держит файл открытым, на сервере папка «не видна», а каталог с 50 000 файлов обрабатывается полчаса. Каждую из этих ситуаций можно предусмотреть заранее, и все примеры кода здесь — для платформы 8.3.

Как получить список файлов каталога: НайтиФайлы и объект Файл

Основной инструмент — глобальная функция НайтиФайлы(Путь, Маска, ИскатьВПодкаталогах). Она возвращает массив объектов типа Файл. Маска поддерживает * и ?, третий параметр включает рекурсивный обход подпапок.

Каталог = "D:\Обмен\Входящие";
Файлы = НайтиФайлы(Каталог, "*.csv", Ложь);

Для Каждого Файл Из Файлы Цикл
    Если Файл.ЭтоКаталог() Тогда
        Продолжить;
    КонецЕсли;
    Сообщить(Файл.Имя + " — " + Формат(Файл.Размер(), "ЧГ=0") + " байт");
КонецЦикла;

Если маска "*", в массив попадут и вложенные папки. Поэтому проверка ЭтоКаталог() нужна почти всегда.

Что умеет объект Файл

Свойство или методЧто возвращает
Имяимя с расширением: отчёт.csv
ИмяБезРасширенияотчёт
Расширение.csv — с точкой
ПолноеИмяполный путь к файлу
Путьпуть к папке с завершающим разделителем
Существует()есть ли файл на диске
ЭтоКаталог(), ЭтоФайл()тип объекта
Размер()размер в байтах
ПолучитьВремяИзменения()дата последнего изменения

Объект можно создать и вручную: Новый Файл("D:\Обмен\1.txt"). Конструктор не проверяет существование файла — это делает только Существует().

НайтиФайлы не гарантирует порядок. Если файлы нужно обработать по очереди поступления, сложите их в таблицу значений с колонкой «Изменен» и отсортируйте по ней.

ТЗ = Новый ТаблицаЗначений;
ТЗ.Колонки.Добавить("ПолноеИмя");
ТЗ.Колонки.Добавить("Изменен");

Для Каждого Файл Из НайтиФайлы(Каталог, "*.xml") Цикл
    Стр = ТЗ.Добавить();
    Стр.ПолноеИмя = Файл.ПолноеИмя;
    Стр.Изменен = Файл.ПолучитьВремяИзменения();
КонецЦикла;
ТЗ.Сортировать("Изменен Возр");

Чтение текстовых файлов и выбор кодировки

Для текста в 1С есть два объекта. Выбор зависит от размера файла и от того, что с ним делать дальше.

ОбъектКак читаетКогда подходит
ТекстовыйДокументвесь файл в память, доступ к строке по номерунебольшие файлы, нужен ПолучитьСтроку(N)
ЧтениеТекстапострочно или целикомбольшие файлы, потоковая обработка

Построчное чтение через ЧтениеТекста

Чтение = Новый ЧтениеТекста(ИмяФайла, КодировкаТекста.UTF8);
Строка = Чтение.ПрочитатьСтроку();
Пока Строка <> Неопределено Цикл
    Поля = СтрРазделить(Строка, ";", Истина);
    // обработка полей
    Строка = Чтение.ПрочитатьСтроку();
КонецЦикла;
Чтение.Закрыть();

ПрочитатьСтроку() возвращает Неопределено, когда файл кончился. Пустая строка в середине файла приходит как "", поэтому сравнивайте именно с Неопределено, а не с пустой строкой.

Весь файл одной строкой читает Чтение.Прочитать() без параметров. Для ТекстовыйДокумент — метод Прочитать(ИмяФайла, Кодировка), затем КоличествоСтрок() и ПолучитьСтроку(НомерСтроки).

Какую кодировку указать

Кодировка — главная причина кракозябр при чтении файлов в 1С. Варианты:

  • 📄 КодировкаТекста.UTF8 — выгрузки сайтов, банков, маркетплейсов, большинство современных систем;
  • 🪟 КодировкаТекста.ANSI — файлы из Блокнота старых Windows и выгрузки «по-старинке», в русской Windows это 1251;
  • 💾 КодировкаТекста.OEM — DOS-кодировка 866, встречается в выводе консольных утилит;
  • 🔤 КодировкаТекста.UTF16 — некоторые выгрузки из Excel («Текст Юникод»);
  • 🏷 строка с именем кодировки — "windows-1251", "koi8-r" — если нужна конкретная.

Определить кодировку на глаз можно по первым байтам. Файлы UTF-8 с меткой начинаются с EF BB BF, UTF-16 LE — с FF FE. Если метки нет, а в файле русский текст, пробуйте UTF-8, затем ANSI.

ДД = Новый ДвоичныеДанные(ИмяФайла);
Буфер = ПолучитьБуферДвоичныхДанныхИзДвоичныхДанных(ДД);
Если Буфер.Размер >= 3
    И Буфер[0] = 239 И Буфер[1] = 187 И Буфер[2] = 191 Тогда
    Кодировка = КодировкаТекста.UTF8;
Иначе
    Кодировка = КодировкаТекста.ANSI;
КонецЕсли;

Этот пример загружает весь файл в память. Для файлов в сотни мегабайт читайте только первые байты через поток — пример ниже.

Двоичные файлы: ФайловыеПотоки, ЧтениеДанных, ДвоичныеДанные

Картинки, PDF, архивы и файлы со своим форматом читают как байты. Начиная с платформы 8.3.9 для этого есть потоки: ФайловыеПотоки, ФайловыйПоток, БуферДвоичныхДанных, ЧтениеДанных.

Три способа и чем они отличаются

СпособПамятьКогда использовать
Новый ДвоичныеДанные(ИмяФайла)весь файл сразуфайл нужно целиком сохранить в реквизит или хранилище
ФайловыеПотоки.ОткрытьДляЧтения() + буферпорциямибольшие файлы, нужны только заголовок или часть
Новый ЧтениеДанных(ИмяФайла)порциямиразбор структуры: байты, целые числа, строки подряд

Чтение первых байтов через поток

Поток = ФайловыеПотоки.ОткрытьДляЧтения(ИмяФайла);
Буфер = Новый БуферДвоичныхДанных(4);
Прочитано = Поток.Прочитать(Буфер, 0, 4);
Поток.Закрыть();

Если Прочитано = 4 И Буфер[0] = 37 И Буфер[1] = 80
    И Буфер[2] = 68 И Буфер[3] = 70 Тогда
    // файл начинается с «%PDF» — это PDF
КонецЕсли;

Прочитать возвращает число реально прочитанных байт. В конце файла оно меньше запрошенного — по нему и останавливают цикл чтения порциями.

Порционное чтение большого файла

РазмерПорции = 1048576; // 1 МБ
Поток = ФайловыеПотоки.ОткрытьДляЧтения(ИмяФайла);
Буфер = Новый БуферДвоичныхДанных(РазмерПорции);
Всего = 0;
Пока Истина Цикл
    Прочитано = Поток.Прочитать(Буфер, 0, РазмерПорции);
    Если Прочитано = 0 Тогда
        Прервать;
    КонецЕсли;
    Всего = Всего + Прочитано;
    // обработка Прочитано байт из Буфер
КонецЦикла;
Поток.Закрыть();

Поток можно передать и в ЧтениеТекста: Новый ЧтениеТекста(Поток, КодировкаТекста.UTF8). Так удобно читать текст, который пришёл не файлом, а двоичными данными из временного хранилища: ДД.ОткрытьПотокДляЧтения().

Для форматов с готовыми объектами разбор вручную не нужен: ЧтениеXML.ОткрытьФайл(), ЧтениеJSON.ОткрытьФайл(), ЧтениеZipФайла для архивов.

Ошибки и занятые файлы: как не уронить обработку

Чтение файла — внешняя операция, и падать она будет. Файл удалили между НайтиФайлы и открытием, нет прав, файл занят. Каждое открытие оборачивайте в Попытка, иначе одна ошибка остановит обработку всего каталога.

Для Каждого Файл Из Файлы Цикл
    Чтение = Неопределено;
    Попытка
        Чтение = Новый ЧтениеТекста(Файл.ПолноеИмя, КодировкаТекста.UTF8, , , Ложь);
        Текст = Чтение.Прочитать();
        Чтение.Закрыть();
        ОбработатьТекст(Текст);
    Исключение
        Если Чтение <> Неопределено Тогда
            Чтение.Закрыть();
        КонецЕсли;
        ЗаписьЖурналаРегистрации("Загрузка файлов",
            УровеньЖурналаРегистрации.Ошибка, , Файл.ПолноеИмя,
            ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
    КонецПопытки;
КонецЦикла;

Здесь ОбработатьТекст — ваша процедура разбора. Ошибка пишется в журнал регистрации, цикл идёт дальше.

Почему файл «занят» и что с этим делать

У конструктора ЧтениеТекста последний параметр — Монопольно, и по умолчанию он равен Истина. Платформа пытается открыть файл эксклюзивно. Если файл в этот момент открыт в Excel или его дописывает другая программа, открытие падает.

Что помогает:

  1. 🔓 Передать Монопольно = Ложь, как в примере выше. Это самый частый приём, о котором забывают.
  2. 📋 Скопировать файл во временный через КопироватьФайл(Источник, ПолучитьИмяВременногоФайла("csv")) и читать копию. Работает, если программа-владелец разрешает чтение другим.
  3. ⏳ Пропустить файл и вернуться к нему при следующем запуске регламентного задания.

Не читайте файл, который ещё записывается. Внешняя система могла выложить только половину выгрузки. Договоритесь о признаке готовности: запись во временное имя .tmp с переименованием в конце или файл-флаг .ready рядом.

Не забывайте закрывать

Незакрытый ЧтениеТекста или поток держит файл до конца сеанса или до сборки мусора. Следствие — файл нельзя удалить или переместить, а на сервере это может длиться долго. Вызывайте Закрыть() явно, в том числе в ветке Исключение.

Массовое чтение каталога: как ускорить

На каталоге из нескольких тысяч файлов разница между аккуратным и небрежным кодом — минуты против секунд. Что работает:

  • 🎯 Узкая маска. НайтиФайлы(Каталог, "*.xml") быстрее, чем "*" с фильтрацией по Расширение в цикле.
  • 🚫 Без лишних обращений к диску. Размер(), ПолучитьВремяИзменения(), Существует() — каждое идёт в файловую систему. Не вызывайте их в цикле по несколько раз, сохраните значение в переменную.
  • 📏 Подходящий объект. ТекстовыйДокумент на файле в 300 МБ съест память рабочего процесса. Для больших файлов — только ЧтениеТекста построчно или потоки.
  • 🧺 Порции. Не читайте 10 000 файлов в один массив данных перед записью. Обработали файл — записали результат — освободили переменные.
  • 📦 Архивная папка. Обработанные файлы переносите ПереместитьФайл() в подпапку Обработано. Тогда следующий запуск не перебирает уже загруженное, и каталог не разрастается.
  • 🔁 Транзакции поменьше. Если по каждому файлу пишутся документы, одна транзакция на файл лучше, чем одна на весь каталог: ошибка в 9 999-м файле не откатит предыдущие.

Рекурсивный поиск по дереву с десятками тысяч файлов (ИскатьВПодкаталогах = Истина) возвращает огромный массив целиком. Если нужно обойти большое дерево, ищите по одной папке, а подпапки получайте отдельным вызовом с маской "*" и проверкой ЭтоКаталог().

Перед запуском загрузки из каталога
Выполнено: 0 / 7

Файловая и клиент-серверная база: где на самом деле лежит каталог

Самая частая путаница: код верный, а НайтиФайлы возвращает пустой массив. Причина — код выполняется не там, где лежат файлы.

Сервер читает свой диск

Код в модуле с директивой &НаСервере, в общем модуле с флагом «Сервер» или в регламентном задании выполняется рабочим процессом rphost на сервере 1С. Путь D:\Обмен — это диск D: сервера, а не компьютера пользователя.

Учтите при этом:

  • 👤 рабочий процесс работает от имени учётной записи службы сервера 1С (при стандартной установке — USR1CV8), а не от имени пользователя в базе;
  • 🌐 сетевые диски, подключённые буквой у пользователя, серверу не видны — указывайте UNC-путь: \\fileserver\obmen\in;
  • 🔐 у учётной записи службы должны быть права на чтение этой сетевой папки;
  • 🐧 на сервере под Linux пути вида /mnt/obmen/in, регистр в именах файлов различается.

В файловом варианте серверный код выполняется на компьютере пользователя (или на веб-сервере при публикации), поэтому там такой проблемы обычно нет. Проблема всплывает при переносе базы на сервер.

Клиент читает диск пользователя

Если файлы лежат на компьютере пользователя, читать их нужно на клиенте, а данные передавать на сервер. Прямой доступ клиента к серверному диску и наоборот невозможен.

Схема для управляемой формы:

  1. Пользователь выбирает каталог в ДиалогВыбораФайла с режимом РежимДиалогаВыбораФайла.ВыборКаталога.
  2. На клиенте ищете файлы. В современных конфигурациях синхронные вызовы отключены, поэтому используйте асинхронный вариант: Файлы = Ждать НайтиФайлыАсинх(Каталог, "*.csv") (платформа 8.3.18 и новее).
  3. Каждый файл передаёте на сервер во временное хранилище — Ждать ПоместитьФайлНаСерверАсинх(...) или ПоместитьФайлыНаСерверАсинх(...), — получаете адрес.
  4. На сервере ПолучитьИзВременногоХранилища(Адрес) возвращает ДвоичныеДанные. Дальше ДД.ОткрытьПотокДляЧтения() и ЧтениеТекста по потоку, как в разделе про потоки.

В веб-клиенте без расширения для работы с файлами браузер не даст обойти каталог: доступен только выбор отдельных файлов. Для каталога нужно установленное расширение работы с файлами или тонкий клиент.

Что выбрать под задачу

ЗадачаГде читать
Регулярная загрузка из папки обменарегламентное задание на сервере, UNC-путь
Разовая загрузка файлов пользователемклиент → временное хранилище → сервер
Файловая база на одном компьютереможно читать прямо на сервере (это тот же компьютер)
Публикация на веб-серверетолько через передачу файлов с клиента

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

Как получить только имена файлов без пути?
Свойство Имя объекта Файл даёт имя с расширением, ИмяБезРасширения — без него. Полный путь — в ПолноеИмя.
Можно ли задать в НайтиФайлы несколько масок, например *.xls и *.xlsx?
Нет, маска одна. Вызовите функцию дважды и объедините массивы или ищите по "*.xls*", если это допустимо по составу папки.
Как проверить, что каталог существует, до поиска файлов?
Новый Файл(Каталог) и затем Существует() и ЭтоКаталог(). Если каталога нет, НайтиФайлы просто вернёт пустой массив без ошибки, и это легко принять за пустую папку.
Почему на сервере путь с буквой диска Z: не работает?
Буква Z: — сетевой диск, подключённый в сеансе пользователя Windows. Служба сервера 1С его не видит. Используйте путь \\сервер\папка и выдайте права учётной записи службы.
Как удалить файл после успешной загрузки?
УдалитьФайлы(ПолноеИмя) — только после Закрыть() у всех объектов чтения. Надёжнее не удалять, а переносить в архивную папку через ПереместитьФайл(): так проще разобраться, если загрузка дала неверный результат.