Чтение каталога из встроенного языка 1С: поиск файлов, фильтры, подпапки
Содержание
Для списка файлов в папке 1С не нужен никакой «файловый менеджер» из справочников и обработок. Достаточно глобальной функции НайтиФайлы(Каталог, Маска, ИскатьВПодкаталогах). Она возвращает массив объектов Файл, и дальше с ним работают обычным циклом.
Большинство проблем начинается не с самой функции, а с контекста. Где выполняется код (клиент или сервер), какая маска, что с подкаталогами, сколько файлов в папке. Разберём это по порядку на примерах для платформы 8.3.
Что возвращает НайтиФайлы и как её вызвать
Функция принимает три параметра:
| Параметр | Что это | Если опустить |
|---|---|---|
Каталог | Путь к папке, в которой ищем | обязателен |
Маска | Шаблон имени, например *.xlsx | вернутся все элементы каталога |
ИскатьВПодкаталогах | Истина — обходить вложенные папки | Истина |
Последний пункт важен: по умолчанию поиск идёт с заходом в подкаталоги. Если нужна только верхняя папка, передайте Ложь явно.
Минимальный рабочий пример:
Каталог = "C:\Обмен\Входящие";
Файлы = НайтиФайлы(Каталог, "*", Ложь);
Для Каждого Элемент Из Файлы Цикл
Сообщить(Элемент.Имя);
КонецЦикла;
Результат содержит и файлы, и папки. Различает их метод ЭтоКаталог(). Скрытые и системные файлы тоже попадают в выборку, если у процесса есть права на чтение.
Что умеет объект Файл
У каждого элемента массива есть свойства и методы, которых хватает для 90% задач:
Имя— имя вместе с расширением,Отчёт.xlsx;ИмяБезРасширения—Отчёт;Расширение— с точкой:.xlsx;Путь— папка, в которой лежит файл;ПолноеИмя— путь вместе с именем;Размер()— размер в байтах;ПолучитьВремяИзменения()— дата последней правки;ЭтоКаталог()иЭтоФайл()— тип элемента;Существует()— на момент вызова файл ещё на месте.
Расширение приходит с точкой. Сравнение Элемент.Расширение = "xlsx" никогда не сработает, нужно ".xlsx".
Где выполнять код: клиент или сервер
Путь C:\Обмен на клиенте — это диск рабочего места пользователя. На сервере — диск сервера 1С. Это разные машины, и в клиент-серверной базе путь, годный на клиенте, на сервере обычно не существует.
Что нужно запомнить:
- Серверный вызов
НайтиФайлысмотрит в файловую систему сервера и работает под учётной записью службы сервера 1С. Права пользователя Windows тут не участвуют. - Для сетевых папок на сервере используйте UNC-путь
\\сервер\ресурс\папка. Буквы сетевых дисков служба не видит. - На клиенте веб-доступа и в браузере прямой доступ к диску закрыт. Нужно подключённое расширение работы с файлами, иначе платформа откажет в доступе или предложит его установить.
- Тонкий клиент работает с локальным диском без расширений, но вызов блокирующий: форма «зависает», пока идёт обход.
Если файлы лежат на сервере, а нужны пользователю, считайте список в серверной процедуре и возвращайте клиенту не объекты Файл, а массив структур с именем, размером и датой. Так проще и быстрее.
&НаСервереБезКонтекста
Функция СписокФайловНаСервере(Каталог)
Результат = Новый Массив;
Для Каждого Элемент Из НайтиФайлы(Каталог, "*", Ложь) Цикл
Если Элемент.ЭтоКаталог() Тогда
Продолжить;
КонецЕсли;
Результат.Добавить(Новый Структура("Имя, Размер, Изменён",
Элемент.Имя, Элемент.Размер(), Элемент.ПолучитьВремяИзменения()));
КонецЦикла;
Возврат Результат;
КонецФункции
Фильтр по расширению и маске имени
Маска понимает два символа: * (любая последовательность) и ? (один символ). Примеры:
| Маска | Найдёт |
|---|---|
*.xml | все XML |
Счёт_*.pdf | Счёт_001.pdf, Счёт_ИП.pdf |
отчёт??.xlsx | отчёт01.xlsx, но не отчёт1.xlsx |
* | всё, что есть в папке |
Для «всех файлов» пишите *, а не *.*. В Windows оба варианта дают тот же результат, а на сервере под Linux *.* пропустит файлы без точки в имени.
Несколько расширений сразу
Маска одна, перечислить в ней *.xls;*.xlsx нельзя. Либо вызывайте функцию дважды, либо берите всё и отбирайте вручную:
Допустимые = Новый Массив;
Допустимые.Добавить(".xls");
Допустимые.Добавить(".xlsx");
Результат = Новый Массив;
Для Каждого Элемент Из НайтиФайлы(Каталог, "*", Ложь) Цикл
Если Элемент.ЭтоФайл() И Допустимые.Найти(НРег(Элемент.Расширение)) <> Неопределено Тогда
Результат.Добавить(Элемент);
КонецЕсли;
КонецЦикла;
НРег нужен, потому что Договор.PDF и Договор.pdf для файловой системы Linux — разные файлы, а пользователи сохраняют как попало.
Отбор по размеру и дате
Платформа не умеет фильтровать по размеру или дате на стороне поиска. Это делается в цикле после получения списка:
Граница = НачалоДня(ТекущаяДата()) - 7 * 86400;
Для Каждого Элемент Из НайтиФайлы(Каталог, "*.csv", Ложь) Цикл
Если Элемент.ПолучитьВремяИзменения() >= Граница И Элемент.Размер() > 0 Тогда
// свежий непустой файл за последние 7 суток
КонецЕсли;
КонецЦикла;
Сначала отсекайте маской, потом проверяйте размер и дату: каждый вызов Размер() или ПолучитьВремяИзменения() — это обращение к диску.
Обход вложенных папок
Самый короткий способ — третий параметр Истина:
Файлы = НайтиФайлы("C:\Архив", "*.pdf", Истина);
В результате окажутся PDF из всех уровней. Чтобы понять, из какой подпапки файл, смотрите Элемент.Путь.
Ручной рекурсивный обход нужен в трёх случаях:
- надо пропускать определённые папки (
Temp,Корзина, служебные); - надо ограничить глубину;
- надо отчитываться о прогрессе по папкам.
Процедура ОбойтиКаталог(Каталог, Результат, Глубина = 0, МаксГлубина = 3)
Если Глубина > МаксГлубина Тогда
Возврат;
КонецЕсли;
Для Каждого Элемент Из НайтиФайлы(Каталог, "*", Ложь) Цикл
Если Элемент.ЭтоКаталог() Тогда
Если Элемент.Имя <> "Temp" Тогда
ОбойтиКаталог(Элемент.ПолноеИмя, Результат, Глубина + 1, МаксГлубина);
КонецЕсли;
Иначе
Результат.Добавить(Элемент);
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Следите за символическими ссылками: ссылка и символические ссылки на родительскую папку может привести к циклу. Ограничение глубины защитит от бесконечной рекурсии.
Проверка пути и защита от ошибок
Типичный сценарий: пользователь ввёл путь руками, папки нет или она недоступна. Проверяйте заранее, а не ловите непонятное сообщение из недр платформы.
Папка = Новый Файл(Каталог);
Если Не Папка.Существует() Тогда
ВызватьИсключение "Папка не найдена: " + Каталог;
КонецЕсли;
Если Не Папка.ЭтоКаталог() Тогда
ВызватьИсключение "Указан файл, а не папка: " + Каталог;
КонецЕсли;
Затем оберните сам обход в Попытка … Исключение, потому что доступ может пропасть в процессе (сетевой ресурс отвалился, папку заблокировали):
Попытка
Файлы = НайтиФайлы(Каталог, "*", Ложь);
Исключение
ЗаписьЖурналаРегистрации("Импорт.СписокФайлов", УровеньЖурналаРегистрации.Ошибка,
, , ОписаниеОшибки());
Файлы = Новый Массив;
КонецПопытки;
Что чаще всего идёт не так
- ❌ Путь на клиенте указали правильно, а код выполняется на сервере. Результат — пустой массив или отказ в доступе.
- ❌ Путь без завершающего разделителя при склейке:
Каталог + ИмядаётC:\ОбменФайл.xml. Добавляйте\или используйте разделитель, подходящий ОС сервера. - ❌ Сетевой диск
Z:на сервере. Служба его не видит, нужен UNC-путь. - ❌ У учётной записи службы нет прав на чтение папки. Выдайте права именно ей, а не пользователю, который сидит в клиенте.
- ❌ Файл удалили между поиском и обработкой. Перед открытием проверьте
Элемент.Существует(). - ❌ В имени кириллица на сервере под Linux. Проверьте кодировку локали сервера.
Если папка огромная: скорость и память
Когда в каталоге десятки тысяч файлов, НайтиФайлы отрабатывает дольше и забирает память: все объекты Файл создаются сразу. Ориентир по опыту: тысяча элементов на локальном SSD обходится за доли секунды, а сто тысяч на сетевом ресурсе — десятки секунд.
Что помогает:
- Узкая маска. Не забирайте
*, если нужны только*.xml. Меньше объектов — меньше обращений к диску. - Не вызывать
Размер()иПолучитьВремяИзменения()без необходимости. Каждый вызов — отдельный запрос к файловой системе, особенно заметно по сети. - Серверный вызов вместо клиентского. Сервер ближе к хранилищу, а пользовательская сессия не блокируется на клиенте.
- Фоновое задание. Длинный обход выносите в регламентное или фоновое задание и сохраняйте результат в регистр сведений , а форме отдавайте уже готовое.
- Порции. Если надо обработать всё, берите список целиком, но обрабатывайте по 500–1000 элементов, записывая прогресс между порциями.
Как замерить время обхода
Прежде чем оптимизировать, измерьте. Возьмите ТекущаяУниверсальнаяДатаВМиллисекундах() до и после вызова и запишите разницу в журнал. Так вы увидите, что тормозит: сам поиск, последующий цикл с Размер() или обработка файлов. Часто оказывается, что поиск занимает секунду, а десять минут уходят на чтение содержимого, и ускорять надо именно его.
Асинхронный поиск в клиентском коде
Для клиента, когда нельзя замораживать интерфейс, есть метод НачатьПоискФайлов. Он принимает описание оповещения, каталог, маску и признак поиска в подкаталогах, а результат приходит в процедуру-обработчик:
&НаКлиенте
Процедура НайтиФайлыАсинхронно(Каталог)
Оповещение = Новый ОписаниеОповещения("ПослеПоискаФайлов", ЭтотОбъект);
НачатьПоискФайлов(Оповещение, Каталог, "*.xlsx", Ложь);
КонецПроцедуры
&НаКлиенте
Процедура ПослеПоискаФайлов(НайденныеФайлы, ДополнительныеПараметры) Экспорт
Для Каждого Элемент Из НайденныеФайлы Цикл
// обработка найденного
КонецЦикла;
КонецПроцедуры
Такой вариант не блокирует окно и хорошо подходит для веб-клиента с установленным расширением.
Типовые задачи на основе списка файлов
Сам по себе список редко нужен. Обычно за ним идёт обработка. Три сценария встречаются чаще остальных.
Загрузка входящих файлов по расписанию
Регламентное задание раз в несколько минут смотрит папку обмена, обрабатывает каждый новый файл и переносит его в подпапку Обработано. Порядок такой:
- Получить список по маске, без подкаталогов.
- Отсортировать по времени изменения, чтобы старые файлы шли первыми.
- Пропустить файлы, которые ещё пишет внешняя система: размер нулевой или дата изменения свежее минуты.
- Обработать файл и переместить его командой
ПереместитьФайл(Источник, Приёмник). - При ошибке перенести в
Ошибкии записать причину в журнал регистрации.
Пункт 3 спасает от самой неприятной ошибки: файл найден и прочитан, а внешняя программа ещё не закончила его писать. Загрузка проходит наполовину.
Очистка старых файлов
Например, удалить из папки выгрузок всё старше 30 дней. Берёте дату границы, идёте по списку и вызываете УдалитьФайлы(Элемент.ПолноеИмя) для подходящих. Перед первым запуском на боевых данных прогоните цикл без удаления и выведите список в журнал: ошибка в границе даты сотрёт нужное.
Сверка папки с данными в базе
Типичный пример: в базе 1 200 вложений, на диске лежит 1 187 файлов. Нужно найти расхождение. Собираете имена из папки в соответствие (Новый Соответствие), где ключ — имя файла, и проверяете каждую запись базы через Соответствие.Получить(Имя). Это быстрее, чем искать в массиве: поиск по ключу не перебирает все элементы.
Сравнивайте имена в одном регистре: приведите обе стороны через НРег. Иначе в Windows Акт.PDF и акт.pdf — один файл, а у вас они разойдутся.
Как вывести результат: таблица для пользователя
Массив Файл неудобен для показа, и в форму его не положишь. Соберите таблицу значений и отсортируйте:
Таблица = Новый ТаблицаЗначений;
Таблица.Колонки.Добавить("Имя");
Таблица.Колонки.Добавить("Размер");
Таблица.Колонки.Добавить("Изменён");
Для Каждого Элемент Из НайтиФайлы(Каталог, "*", Ложь) Цикл
Если Элемент.ЭтоФайл() Тогда
Строка = Таблица.Добавить();
Строка.Имя = Элемент.Имя;
Строка.Размер = Элемент.Размер();
Строка.Изменён = Элемент.ПолучитьВремяИзменения();
КонецЕсли;
КонецЦикла;
Таблица.Сортировать("Изменён Убыв");
Дальше таблицу показывают в табличном документе или в реквизите формы типа ТаблицаЗначений.
НайтиФайлы с маской и Ложь для подкаталогов.НайтиФайлы с Истина. Если надо исключать папки или ограничить глубину, пишите свой рекурсивный обход.НачатьПоискФайлов и обработчик оповещения.Частые вопросы
Почему НайтиФайлы возвращает пустой массив, хотя файлы есть?
Можно ли отобрать сразу `.xls` и `.xlsx` одной маской?
Расширение.Как узнать, это файл или папка?
ЭтоКаталог() и ЭтоФайл() объекта Файл. Проверка по расширению ненадёжна: папка может называться Архив.2024.Как получить только имена без расширения?
ИмяБезРасширения. Для полного пути используйте ПолноеИмя.Видит ли НайтиФайлы файлы внутри архивов?
ЧтениеZipФайла.