Обход каталога на встроенном языке 1С: от простого цикла до тысяч файлов
Содержание
Чтобы перебрать все файлы в папке в 1С, вызовите НайтиФайлы(Путь, "*", Ложь) и пройдите циклом Для Каждого по полученному массиву. Каждый элемент — объект Файл с именем, расширением, полным путём и размером. Третий параметр Истина включает поиск во всех вложенных папках одной строкой кода.
Способов по сути два. Встроенный флаг рекурсии хорош, когда нужен плоский список всех файлов дерева. Собственная рекурсивная процедура нужна, когда надо пропускать служебные каталоги, ограничивать глубину или считать что-то по каждой папке отдельно. Всё ниже работает на платформе 8.3 в коде на сервере и в толстом клиенте; про тонкий и веб-клиент — отдельный раздел.
Чем 1С работает с файлами: НайтиФайлы и объект Файл
Отдельного «файлового менеджера» во встроенном языке нет. Вся работа держится на функции глобального контекста и одном объекте.
| Что | Назначение |
|---|---|
НайтиФайлы(Путь, Маска, ИскатьВПодкаталогах) | Возвращает Массив объектов Файл |
Новый Файл(ПолноеИмя) | Описание одного файла или каталога |
КопироватьФайл, ПереместитьФайл, УдалитьФайлы | Операции над найденным |
СоздатьКаталог(Путь) | Создание папки, включая промежуточные |
Основные свойства и методы объекта Файл:
- 📄
Имя— имя с расширением:отчет.xml; - 🏷
ИмяБезРасширения—отчет; - 🔤
Расширение— с точкой:.xml; - 📁
Путь— каталог, где лежит файл, с завершающим разделителем; - 🧭
ПолноеИмя— путь целиком, его и передают в другие функции; - ✅
Существует(),ЭтоФайл(),ЭтоКаталог()— проверки; - 📏
Размер()— в байтах; - 🕒
ПолучитьВремяИзменения()— дата последнего изменения.
Свойства Имя, Расширение, ПолноеИмя берутся из строки пути и к диску не обращаются. А вот Размер(), ЭтоКаталог(), ПолучитьВремяИзменения() каждый раз идут в файловую систему. На больших папках это заметно.
Простой перебор файлов в одной папке
Базовый вариант: взять все элементы каталога без подпапок и вывести имена.
&НаСервереБезКонтекста
Процедура ПеречислитьФайлы(Каталог)
Найденные = НайтиФайлы(Каталог, "*", Ложь);
Для Каждого ЭлементФайл Из Найденные Цикл
Если ЭлементФайл.ЭтоКаталог() Тогда
Продолжить;
КонецЕсли;
Сообщить(ЭлементФайл.Имя + " — " + Формат(ЭлементФайл.Размер(), "ЧГ=0") + " байт");
КонецЦикла;
КонецПроцедуры
Три момента, на которых спотыкаются чаще всего:
- Маска
"*"возвращает и файлы, и папки. Если нужны только файлы — проверкаЭтоКаталог()обязательна. - Число склеивают со строкой через
Формат(..., "ЧГ=0"). Иначе размер в 15 000 байт выведется как «15 000» с неразрывным пробелом, а в имени файла такая строка вообще ломает путь. - Порядок элементов в массиве не гарантирован. Нужна сортировка — собирайте результат в таблицу значений.
Сортировка по дате изменения, новые сверху:
Таблица = Новый ТаблицаЗначений;
Таблица.Колонки.Добавить("ПолноеИмя");
Таблица.Колонки.Добавить("Изменен");
Для Каждого ЭлементФайл Из НайтиФайлы(Каталог, "*.xml", Ложь) Цикл
Строка = Таблица.Добавить();
Строка.ПолноеИмя = ЭлементФайл.ПолноеИмя;
Строка.Изменен = ЭлементФайл.ПолучитьВремяИзменения();
КонецЦикла;
Таблица.Сортировать("Изменен Убыв");
Рекурсивный перебор всех файлов во вложенных папках
Вариант 1. Флаг ИскатьВПодкаталогах
Самый короткий путь — передать Истина третьим параметром:
ВсеФайлы = НайтиФайлы("D:\Обмен", "*", Истина);
Платформа сама обойдёт дерево и вернёт один плоский массив. Подходит для задач «найти все PDF в архиве» или «посчитать общий объём».
Минусы: нельзя исключить отдельную ветку (например, Архив или .git), нельзя остановиться на глубине 2, а весь массив строится в памяти до того, как вы обработаете первый файл.
Вариант 2. Своя рекурсивная процедура
Здесь вы управляете обходом сами: какие папки пропускать, на какой глубине остановиться, что делать с каждым файлом.
&НаСервереБезКонтекста
Процедура ОбойтиКаталог(Каталог, Результат, Глубина = 0, МаксГлубина = 10)
Если Глубина > МаксГлубина Тогда
Возврат;
КонецЕсли;
Для Каждого Элемент Из НайтиФайлы(Каталог, "*", Ложь) Цикл
Если Элемент.ЭтоКаталог() Тогда
Если НРег(Элемент.Имя) = "архив" Тогда
Продолжить; // эту ветку не обходим
КонецЕсли;
ОбойтиКаталог(Элемент.ПолноеИмя, Результат, Глубина + 1, МаксГлубина);
Иначе
Результат.Добавить(Элемент.ПолноеИмя);
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Вызов:
Список = Новый Массив;
ОбойтиКаталог("D:\Обмен", Список);
Сообщить("Найдено файлов: " + Формат(Список.Количество(), "ЧН=0; ЧГ=0"));
Ограничение глубины — не украшение. Символические ссылки и точки соединения NTFS могут замкнуть дерево само на себя, и без предела процедура уйдёт в бесконечную рекурсию до переполнения стека.
Когда какой вариант брать:
| Задача | Флаг Истина | Своя рекурсия |
|---|---|---|
| Плоский список всех файлов | ✅ проще | можно |
| Пропустить отдельные папки | ❌ только фильтром после | ✅ |
| Ограничить глубину | ❌ | ✅ |
| Итоги по каждой папке | ❌ | ✅ |
| Обработка «на лету» без большого массива | ❌ | ✅ |
Фильтр файлов по маске и расширению
Маска во втором параметре поддерживает символы * и ?:
"*.xml"— все XML;"Выгрузка_*.csv"— выгрузки с префиксом;"??.txt"— файлы с именем из двух символов.
Маска одна на вызов. Список вида "*.xml;*.json" не сработает. Для нескольких расширений есть два пути: вызвать НайтиФайлы по разу на каждое или взять всё маской "*" и фильтровать в цикле.
Фильтр по набору расширений через соответствие:
Нужные = Новый Соответствие;
Нужные.Вставить(".xml", Истина);
Нужные.Вставить(".json", Истина);
Нужные.Вставить(".csv", Истина);
Для Каждого ЭлементФайл Из НайтиФайлы(Каталог, "*", Истина) Цикл
Если Нужные.Получить(НРег(ЭлементФайл.Расширение)) = Неопределено Тогда
Продолжить;
КонецЕсли;
// обработка файла
КонецЦикла;
НРег здесь не лишний. На сервере 1С под Linux имена файлов чувствительны к регистру, и ОТЧЕТ.XML маской "*.xml" может не найтись. Фильтр по НРег(Расширение) работает одинаково на любой ОС.
Другие частые фильтры:
- 📏 по размеру:
Если ЭлементФайл.Размер() = 0 Тогда Продолжить;— пропустить пустые; - 🕒 по дате:
Если ЭлементФайл.ПолучитьВремяИзменения() < НачалоДня(ТекущаяДата()) Тогда— только сегодняшние; - 🔒 по атрибутам:
ПолучитьНевидимость()иПолучитьТолькоЧтение()— скрытые и защищённые от записи.
Обработка ошибок при переборе файлов
Каталог может не существовать, файл — быть занят другой программой, у пользователя службы может не хватить прав. Код без обработки исключений падает на первом же таком файле и бросает остальные.
Начинайте с проверки каталога. Так вы отличите «папки нет» от «папка пуста»:
Папка = Новый Файл(Каталог);
Если Не Папка.Существует() Или Не Папка.ЭтоКаталог() Тогда
ВызватьИсключение "Каталог не найден: " + Каталог;
КонецЕсли;
Обработку каждого файла оборачивайте в Попытка, чтобы один сбой не останавливал весь обход:
Ошибки = Новый Массив;
Для Каждого ЭлементФайл Из НайтиФайлы(Каталог, "*.txt", Ложь) Цикл
Попытка
Текст = Новый ТекстовыйДокумент;
Текст.Прочитать(ЭлементФайл.ПолноеИмя, КодировкаТекста.UTF8);
// разбор текста
Исключение
Ошибки.Добавить(ЭлементФайл.Имя + ": " + КраткоеПредставлениеОшибки(ИнформацияОбОшибке()));
КонецПопытки;
КонецЦикла;
Если Ошибки.Количество() > 0 Тогда
ЗаписьЖурналаРегистрации("Обработка файлов", УровеньЖурналаРегистрации.Предупреждение,,,
СтрСоединить(Ошибки, Символы.ПС));
КонецЕсли;
Где выполняется код: сервер, тонкий клиент, веб-клиент
Это источник половины вопросов «почему не находит». Код с директивой &НаСервере смотрит на файловую систему сервера 1С. Код &НаКлиенте — на диски компьютера пользователя.
| Режим | Что видит НайтиФайлы | Особенности |
|---|---|---|
| Файловая база, толстый клиент | Диски этого компьютера | Сервер и клиент — одна машина |
Клиент-сервер, &НаСервере | Диски сервера 1С | Права пользователя службы, обычно USR1CV8 |
Тонкий клиент, &НаКлиенте | Диски пользователя | Синхронные вызовы работают, если не запрещены в конфигурации |
| Веб-клиент | Через расширение работы с файлами | Только асинхронные методы |
В конфигурациях, где в свойствах выставлен режим использования синхронных вызовов «Не использовать», клиентский НайтиФайлы выдаст ошибку. На клиенте используйте асинхронную форму. С платформы 8.3.18 удобнее всего Ждать:
&НаКлиенте
Асинх Процедура ПеречислитьНаКлиенте(Команда)
Найденные = Ждать НайтиФайлыАсинх("C:\Загрузка", "*.xml", Ложь);
Для Каждого ЭлементФайл Из Найденные Цикл
Сообщить(ЭлементФайл.Имя);
КонецЦикла;
КонецПроцедуры
Для веб-клиента перед этим подключите расширение работы с файлами.
Не обрабатывайте на клиенте то, что можно обработать на сервере. Каждый вызов сервера из цикла по файлам — отдельный круг по сети. Передайте на сервер одним вызовом массив путей или двоичные данные через временное хранилище.
Что делают с файлами после перебора: рабочие примеры
Загрузка входящих XML с переносом в «Обработанные»
Типовая задача обмена: забрать всё из папки, загрузить, разложить по подпапкам.
&НаСервереБезКонтекста
Процедура ЗагрузитьВходящие(Каталог)
Обработанные = Каталог + "\Обработанные\";
СОшибкой = Каталог + "\Ошибки\";
СоздатьКаталог(Обработанные);
СоздатьКаталог(СОшибкой);
Для Каждого ЭлементФайл Из НайтиФайлы(Каталог, "*.xml", Ложь) Цикл
Чтение = Неопределено;
Попытка
Чтение = Новый ЧтениеXML;
Чтение.ОткрытьФайл(ЭлементФайл.ПолноеИмя);
// разбор и запись данных
Чтение.Закрыть();
ПереместитьФайл(ЭлементФайл.ПолноеИмя, Обработанные + ЭлементФайл.Имя);
Исключение
Если Чтение <> Неопределено Тогда
Чтение.Закрыть();
КонецЕсли;
ПереместитьФайл(ЭлементФайл.ПолноеИмя, СОшибкой + ЭлементФайл.Имя);
КонецПопытки;
КонецЦикла;
КонецПроцедуры
Обход без флага подкаталогов здесь принципиален: иначе на втором запуске код заберёт уже обработанные файлы из вложенных папок. И закрывайте ЧтениеXML до переноса: открытый файл не переместится. Разделитель \ в примере — для сервера на Windows; для Linux используйте ПолучитьРазделительПутиСервера().
Очистка старых файлов
Граница = ТекущаяДатаСеанса() - 30 * 86400; // 30 суток в секундах
Для Каждого ЭлементФайл Из НайтиФайлы("D:\Логи", "*.log", Истина) Цикл
Если ЭлементФайл.ПолучитьВремяИзменения() < Граница Тогда
УдалитьФайлы(ЭлементФайл.ПолноеИмя);
КонецЕсли;
КонецЦикла;
Удаление через УдалитьФайлы идёт мимо корзины. Сначала прогоните цикл с Сообщить вместо удаления и проверьте список.
Поиск дублей по содержимому
Хеши = Новый Соответствие;
Для Каждого ЭлементФайл Из НайтиФайлы(Каталог, "*", Истина) Цикл
Если ЭлементФайл.ЭтоКаталог() Тогда
Продолжить;
КонецЕсли;
Хеш = Новый ХешированиеДанных(ХешФункция.MD5);
Хеш.ДобавитьФайл(ЭлементФайл.ПолноеИмя);
Ключ = ПолучитьHexСтрокуИзДвоичныхДанных(Хеш.ХешСумма);
Если Хеши.Получить(Ключ) <> Неопределено Тогда
Сообщить("Дубль: " + ЭлементФайл.ПолноеИмя + " = " + Хеши.Получить(Ключ));
Иначе
Хеши.Вставить(Ключ, ЭлементФайл.ПолноеИмя);
КонецЕсли;
КонецЦикла;
Как ускорить перебор, когда файлов тысячи
На папке с 50–100 тысячами файлов наивный код работает минутами. Вот что реально помогает.
- Сужайте маску сразу.
"*.pdf"отфильтрует на уровне файловой системы."*"с проверкой расширения в цикле создаст объект на каждый лишний файл. - Не вызывайте методы диска без нужды.
ЭтоКаталог(),Размер(),ПолучитьВремяИзменения()— по обращению к диску на каждый вызов. Если маска"*.xml"и папок с таким окончанием нет, проверку на каталог можно убрать. Значение, нужное дважды, сохраните в переменную. - Не держите в памяти весь массив. Флаг
Истинана огромном дереве строит гигантский массив. Своя рекурсия обрабатывает файлы по одной папке. - Переносите обработку в фоновое задание. Долгий обход в форме блокирует пользователя. Запустите его через
ФоновыеЗадания.Выполнитьс процедурой общего модуля и сообщайте прогресс. - Работайте с локальным диском. По сети каждое обращение к атрибутам файла медленнее в разы. Большие объёмы сначала копируйте на сервер 1С.
- Пишите в базу пачками. Если по каждому файлу создаётся документ, не открывайте транзакцию на весь обход: одна ошибка откатит всё. Разумнее 100–500 файлов на транзакцию.
- Не складывайте всё в одну папку. Если обработку пишете вы, раскладывайте архив по подпапкам по датам: перебор каталога на 5 000 файлов быстрее, чем на 200 000.
Частые вопросы
Можно ли получить только список подпапок, без файлов?
НайтиФайлы(Каталог, "*", Ложь) и оставьте элементы, у которых ЭтоКаталог() возвращает Истина. Отдельной функции для каталогов в платформе нет.Почему файл в UTF-8 читается кракозябрами?
Текст.Прочитать(Путь, КодировкаТекста.UTF8). Для файлов из старых программ под Windows обычно нужна КодировкаТекста.ANSI.Как найти самый новый файл в папке?
ПолучитьВремяИзменения() с запомненным максимумом. Для одного файла это быстрее, чем таблица с сортировкой.Находит ли НайтиФайлы скрытые файлы?
ПолучитьНевидимость() у каждого элемента.Как перебрать файлы внутри ZIP-архива без распаковки на диск?
ЧтениеZipФайла: коллекция Элементы содержит записи архива с именами и путями. Для чтения содержимого конкретную запись всё равно извлекают во временный каталог методом Извлечь.