Перебор и чтение файлов каталога на встроенном языке 1С 8.3

· 10 мин

Содержание

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

Фрагменты кода рассчитаны на платформу 8.3: синхронный и асинхронный перебор, фильтр по расширению и дате, чтение в нужной кодировке, обработка ошибок доступа и ручная рекурсия с ограничением глубины. Код одинаково работает в любой конфигурации, потому что это механизмы платформы, а не БП или УТ.

Какие объекты платформы 1С работают с файлами и папками

Весь набор умещается в несколько функций и типов. Их хватает на типовые задачи загрузки из каталога: выписки, прайсы, CSV из сторонних систем, XML от контрагентов.

Что нужноЧем делатьКомментарий
Получить список файловНайтиФайлы() / НайтиФайлыАсинх()возвращает массив объектов Файл
Узнать свойства файлаобъект Файлимя, расширение, путь, размер, дата изменения
Прочитать текст целикомТекстовыйДокумент.Прочитать()удобно для небольших файлов
Прочитать текст построчноЧтениеТекстаэкономит память на больших файлах
Прочитать файл как байтыНовый ДвоичныеДанные(ИмяФайла)для любых форматов, хеша, отправки
Разобрать XML / JSONЧтениеXML, ЧтениеJSONструктурированные данные
Открыть ExcelТабличныйДокумент.Прочитать()xlsx, xls, ods
Переместить, скопировать, удалитьПереместитьФайл(), КопироватьФайл(), УдалитьФайлы()архивирование после загрузки

Две вспомогательные функции избавляют от проблем с переносимостью между Windows и Linux:

  • 🔹 ПолучитьРазделительПути() — вернёт \ на Windows и / на Linux;
  • 🔹 ПолучитьМаскуВсеФайлы() — вернёт маску «все файлы», правильную для текущей ОС (*.* или *).

У объекта Файл чаще всего нужны:

  • 📄 Имя — имя с расширением: выписка_01.txt;
  • 📄 ИмяБезРасширения — выписка_01;
  • 📄 Расширение — с точкой: .txt;
  • 📄 ПолноеИмя — полный путь, его и передают в методы чтения;
  • 📄 Путь — каталог, где лежит файл;
  • ⚙️ методы ЭтоФайл(), ЭтоКаталог(), Существует(), Размер(), ПолучитьВремяИзменения(), ПолучитьНевидимость().

Свойство Расширение содержит точку. Сравнение Файл.Расширение = "txt" всегда даст Ложь — сравнивайте с ".txt".

Как получить список всех файлов в каталоге 1С

Базовый перебор выглядит так. Код выполняется на сервере или в толстом клиенте — там, где синхронные вызовы разрешены.

Функция СписокФайловПапки(Каталог)

    Результат = Новый Массив;
    Найденные = НайтиФайлы(Каталог, ПолучитьМаскуВсеФайлы(), Ложь);

    Для Каждого Элемент Из Найденные Цикл
        Если Элемент.ЭтоФайл() Тогда
            Результат.Добавить(Элемент.ПолноеИмя);
        КонецЕсли;
    КонецЦикла;

    Возврат Результат;

КонецФункции

Порядок действий:

  1. Передайте путь к каталогу без завершающего разделителя: C:\Обмен\Входящие или \\srv-files\обмен\входящие.
  2. Укажите маску. Для всех файлов — ПолучитьМаскуВсеФайлы().
  3. Третий параметр Ложь — только верхний уровень, без подпапок.
  4. Отсейте каталоги проверкой ЭтоФайл(): НайтиФайлы возвращает и файлы, и папки, подходящие под маску.

Асинхронный вариант для тонкого клиента

В управляемых формах с запретом синхронных вызовов (свойство конфигурации «Режим использования синхронных вызовов расширений платформы и внешних компонент» = «Не использовать») код выше на клиенте упадёт. Начиная с платформы 8.3.18 используйте Асинх / Ждать:

&НаКлиенте
Асинх Процедура ПрочитатьПапку(Команда)

    Найденные = Ждать НайтиФайлыАсинх(КаталогЗагрузки, "*.csv", Ложь);

    Для Каждого Элемент Из Найденные Цикл
        ЭтоФайл = Ждать Элемент.ЭтоФайлАсинх();
        Если ЭтоФайл Тогда
            Сообщить(Элемент.ПолноеИмя);
        КонецЕсли;
    КонецЦикла;

КонецПроцедуры

На платформах ниже 8.3.18 аналог — НачатьПоискФайлов() с ОписаниеОповещения.

Как дать пользователю выбрать папку

Чтобы не вписывать путь в код, покажите диалог выбора каталога:

&НаКлиенте
Асинх Процедура ВыбратьКаталог(Команда)

    Диалог = Новый ДиалогВыбораФайла(РежимДиалогаВыбораФайла.ВыборКаталога);
    Диалог.Заголовок = "Папка с файлами для загрузки";
    Выбрано = Ждать Диалог.ВыбратьАсинх();

    Если Выбрано <> Неопределено Тогда
        КаталогЗагрузки = Выбрано[0];
    КонецЕсли;

КонецПроцедуры

Чтение содержимого текстовых файлов из папки

Способ зависит от размера файла и от того, что вы с ним делаете дальше.

СпособКогда подходитМинус
ТекстовыйДокументфайлы до нескольких мегабайт, нужен доступ к строке по номерувесь файл в памяти
ЧтениеТекстабольшие логи, выгрузки на сотни тысяч строктолько последовательное чтение
ДвоичныеДанные + ПолучитьСтрокуИзДвоичныхДанных()файл уже пришёл с клиента как двоичные данныевесь файл в памяти

Целиком через ТекстовыйДокумент

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

Для НомерСтроки = 1 По Текст.КоличествоСтрок() Цикл
    Строка = Текст.ПолучитьСтроку(НомерСтроки);
    // разбор строки
КонецЦикла;

Нумерация строк в ТекстовыйДокумент начинается с 1, а не с 0.

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

Чтение = Новый ЧтениеТекста(ПолноеИмя, КодировкаТекста.UTF8);
Строка = Чтение.ПрочитатьСтроку();

Пока Строка <> Неопределено Цикл
    Части = СтрРазделить(Строка, ";", Истина);
    // Части[0] — первая колонка, Части[1] — вторая
    Строка = Чтение.ПрочитатьСтроку();
КонецЦикла;

Чтение.Закрыть();

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

Всегда вызывайте Закрыть(): пока объект чтения жив, файл остаётся занят, и его не получится переместить в архив или удалить.

Кодировка: самая частая причина «кракозябр»

  • 🔤 Выгрузки из современных систем и большинство XML — КодировкаТекста.UTF8.
  • 🔤 Старые выгрузки из Windows-программ и клиент-банков — КодировкаТекста.ANSI или строка "windows-1251".
  • 🔤 Файлы из DOS-программ — КодировкаТекста.OEM (cp866).

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

Не только текст: XML, JSON и Excel

Для структурированных файлов не разбирайте строки вручную:

  • 🧩 XML — ЧтениеXML и ОткрытьФайл(ПолноеИмя), затем ФабрикаXDTO.ПрочитатьXML() или ручной проход по узлам;
  • 🧩 JSON — ЧтениеJSON.ОткрытьФайл(ПолноеИмя) и ПрочитатьJSON(Чтение);
  • 🧩 Excel — ТабличныйДокумент.Прочитать(ПолноеИмя), после чего ячейки доступны через ПолучитьОбласть() и Область("R2C1").Текст.

Фильтр файлов в папке по расширению, имени и дате

Маска в НайтиФайлы понимает * (любые символы) и ? (один символ). Примеры:

  • "*.csv" — все CSV;
  • "выписка_*.txt" — файлы, чьё имя начинается с «выписка_»;
  • "2026??.xml" — шесть символов имени, первые четыре фиксированы.

Ограничение: маска одна на вызов. Если нужны сразу .xml и .txt, ищите все файлы и фильтруйте в коде:

Допустимые = Новый Соответствие;
Допустимые.Вставить(".xml", Истина);
Допустимые.Вставить(".txt", Истина);

Для Каждого Элемент Из НайтиФайлы(Каталог, ПолучитьМаскуВсеФайлы()) Цикл

    Если Не Элемент.ЭтоФайл() Тогда
        Продолжить;
    КонецЕсли;

    Если Допустимые[НРег(Элемент.Расширение)] = Неопределено Тогда
        Продолжить;
    КонецЕсли;

    Если Элемент.ПолучитьНевидимость() Тогда
        Продолжить; // скрытые и служебные файлы
    КонецЕсли;

    Если Элемент.ПолучитьВремяИзменения() < НачалоДня(ТекущаяДата()) Тогда
        Продолжить; // только сегодняшние
    КонецЕсли;

    // чтение файла
КонецЦикла;

На Linux-сервере имена файлов чувствительны к регистру: маска *.xml не найдёт ORDER.XML. Поэтому надёжнее искать все файлы и приводить расширение к нижнему регистру через НРег(), как в примере.

Ещё два фильтра, которые часто нужны на практике:

  • 🚫 Пропуск временных файлов Office — их имя начинается с ~$: СтрНачинаетсяС(Элемент.Имя, "~$").
  • 🚫 Пропуск пустых файлов — Элемент.Размер() = 0. Такие появляются, когда внешняя программа ещё не дописала выгрузку.

Где выполняется код и почему 1С «не видит» папку

Если файлы в папке есть, а НайтиФайлы вернул пустой массив, первым делом проверьте, на каком компьютере выполняется код.

Где запускается ваша загрузка?

Отсюда практическое правило:

  1. Файлы на компьютере пользователя — ищите и читайте на клиенте, затем передавайте содержимое на сервер.
  2. Файлы в общей папке, которую видит сервер 1С, — работайте целиком на сервере. Это быстрее: нет передачи данных между клиентом и сервером.
  3. Для сетевой папки выдайте права на чтение (а при архивировании — и на изменение) учётной записи службы 1С. Её имя видно в свойствах службы агента сервера в оснастке «Службы» Windows.

Обработка ошибок при чтении файлов из каталога

Файл может быть открыт другой программой, удалён между поиском и чтением, лежать в папке без прав. Одна такая ошибка без обработки прервёт загрузку всех остальных файлов. Оборачивайте чтение каждого файла в отдельный блок Попытка:

Чтение = Неопределено;

Для Каждого ИмяФайла Из СписокФайлов Цикл

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

    Чтение = Неопределено;

КонецЦикла;

Что здесь сделано:

  • ✅ ошибка одного файла не останавливает цикл;
  • ✅ открытый файл закрывается и в ветке Исключение;
  • ✅ причина пишется в журнал регистрации с именем файла — администратор найдёт её через отбор по событию «Загрузка из каталога»;
  • ✅ переменная Чтение обнуляется, чтобы проверка в следующей итерации не сработала на старом объекте.

Перед вызовом поиска проверьте сам каталог — так пользователь получит понятное сообщение вместо пустого результата:

Папка = Новый Файл(Каталог);
Если Не Папка.Существует() Или Не Папка.ЭтоКаталог() Тогда
    ВызватьИсключение СтрШаблон("Каталог %1 не найден или недоступен", Каталог);
КонецЕсли;

Если внешняя система пишет файлы в ту же папку, из которой вы читаете, договоритесь о схеме «записал во временное имя .tmp — переименовал». Читайте только файлы с итоговым расширением, и полузаписанный файл в загрузку не попадёт.

Рекурсивный обход вложенных папок

Самый короткий путь — третий параметр НайтиФайлы:

ВсеФайлы = НайтиФайлы(Каталог, "*.xml", Истина);

Платформа сама пройдёт по всем уровням вложенности. Минусы: нельзя ограничить глубину, нельзя пропустить отдельные папки (например, Архив), а на дереве с десятками тысяч файлов весь массив собирается в памяти до первого чтения.

Когда нужен контроль, пишите рекурсию сами:

Процедура ОбойтиКаталог(Каталог, Результат, Глубина = 0)

    МаксимальнаяГлубина = 5;
    Если Глубина > МаксимальнаяГлубина Тогда
        Возврат;
    КонецЕсли;

    Для Каждого Элемент Из НайтиФайлы(Каталог, ПолучитьМаскуВсеФайлы(), Ложь) Цикл

        Если Элемент.ЭтоКаталог() Тогда
            Если НРег(Элемент.Имя) = "архив" Тогда
                Продолжить;
            КонецЕсли;
            ОбойтиКаталог(Элемент.ПолноеИмя, Результат, Глубина + 1);
        ИначеЕсли НРег(Элемент.Расширение) = ".xml" Тогда
            Результат.Добавить(Элемент.ПолноеИмя);
        КонецЕсли;

    КонецЦикла;

КонецПроцедуры

Вызов: Файлы = Новый Массив; ОбойтиКаталог("\\srv-files\обмен", Файлы);.

Глубина 5 здесь — страховка от бесконечного обхода по символическим ссылкам и точкам соединения NTFS, которые указывают на родительскую папку.

Полный пример: загрузить все файлы папки и убрать их в архив

Сценарий из практики: банк или склад кладёт CSV в общую папку, регламентное задание раз в 15 минут забирает их, загружает строки и перемещает обработанные файлы в подпапку Архив, а сбойные — в Ошибки.

Процедура ЗагрузитьФайлыИзКаталога(Каталог) Экспорт

    Разделитель = ПолучитьРазделительПути();
    КаталогАрхива = Каталог + Разделитель + "Архив";
    КаталогОшибок = Каталог + Разделитель + "Ошибки";
    СоздатьКаталог(КаталогАрхива);
    СоздатьКаталог(КаталогОшибок);

    Для Каждого Элемент Из НайтиФайлы(Каталог, ПолучитьМаскуВсеФайлы(), Ложь) Цикл

        Если Не Элемент.ЭтоФайл() Или НРег(Элемент.Расширение) <> ".csv" Тогда
            Продолжить;
        КонецЕсли;

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

            ПереместитьФайл(Элемент.ПолноеИмя,
                КаталогАрхива + Разделитель + Элемент.Имя);

            ЗаписьЖурналаРегистрации("Загрузка из каталога",
                УровеньЖурналаРегистрации.Информация, , Элемент.Имя,
                СтрШаблон("Загружено строк: %1", Формат(ЗагруженоСтрок, "ЧГ=0")));

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

    КонецЦикла;

КонецПроцедуры

Что важно в этом примере:

  1. СоздатьКаталог() не падает, если папка уже есть, — вызывать его при каждом запуске безопасно.
  2. Поиск идёт с параметром Ложь. Иначе файлы из Архив при следующем запуске попали бы в загрузку повторно.
  3. Файл перемещается только после успешного чтения всех строк. Если загрузка упала на середине, файл уйдёт в Ошибки, а не в архив.
  4. Счётчик строк выводится через Формат(..., "ЧГ=0"), чтобы в журнале не было пробелов-разделителей разрядов.
  5. ЗагрузитьСтроку() — ваша процедура записи данных в базу.

Если в архиве уже лежит файл с тем же именем, ПереместитьФайл() вызовет исключение. Для ежедневных выгрузок с повторяющимися именами добавляйте к имени в архиве дату и время: Формат(ТекущаяДатаСеанса(), "ДФ=yyyyMMdd_HHmmss").

Чтобы такая загрузка шла по расписанию, процедуру вызывают из обработчика регламентного задания в общем модуле с флажком «Сервер». Права на папку в этом случае нужны учётной записи службы сервера 1С.

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

Можно ли получить количество файлов в папке без перебора?
Да: НайтиФайлы(Каталог, "*.csv").Количество(). Но платформа всё равно соберёт массив объектов, поэтому на десятках тысяч файлов это займёт заметное время.
В каком порядке НайтиФайлы возвращает файлы?
Порядок не гарантируется и зависит от файловой системы. Если важна очерёдность, сложите имена в СписокЗначений и вызовите СортироватьПоЗначению(), или отсортируйте по дате изменения через таблицу значений.
Как прочитать файл, который сейчас открыт в Excel?
Excel держит файл с блокировкой записи. Надёжный обход — скопировать файл функцией КопироватьФайл() во временный каталог (КаталогВременныхФайлов()) и читать копию, затем удалить её через УдалитьФайлы().
Почему в веб-клиенте НайтиФайлы недоступна?
Браузер не даёт странице доступ к диску. В веб-клиенте нужно установить расширение работы с файлами и использовать асинхронные методы, либо дать пользователю выбрать файлы диалогом и передать их на сервер.
Чем отличаются УдалитьФайлы(Каталог, "*.*") и удаление каталога целиком?
Вызов с маской удаляет файлы внутри, папка остаётся. Вызов УдалитьФайлы(Каталог) без маски удаляет каталог вместе со всем содержимым — используйте его только для своих временных папок.