НайтиФайлы и обход папок: список файлов каталога во встроенном языке 1С
Содержание
Чтобы программно получить файлы из каталога в 1С, вызовите глобальную функцию НайтиФайлы(Путь, Маска, ИскатьВПодкаталогах). Она возвращает массив объектов Файл: у каждого есть полное имя, расширение, размер и время изменения. В клиентском коде без синхронных вызовов и в веб-клиенте используйте асинхронный вариант НайтиФайлыАсинх (платформа 8.3.18 и новее) или НачатьПоискФайлов.
Дальше всё упирается в контекст. Код на сервере видит диски сервера, код на клиенте — диски компьютера пользователя. Веб-клиенту для работы с файловой системой нужно расширение работы с файлами. Ниже готовые фрагменты для каждого случая и разбор ошибок, на которых обычно спотыкаются.
Объект Файл и пути: с чем работает код
Работа с файловой системой во встроенном языке идёт через глобальные функции и объект Файл. НайтиФайлы создаёт такие объекты сама, но объект можно получить и вручную: Новый Файл("C:\Обмен\data.xml"). Сам файл при этом не открывается и не создаётся — объект просто описывает путь.
Свойства, которые нужны чаще всего:
| Свойство | Что содержит | Пример для C:\Обмен\data.xml |
|---|---|---|
ПолноеИмя | путь целиком | C:\Обмен\data.xml |
Имя | имя с расширением | data.xml |
ИмяБезРасширения | имя без расширения | data |
Расширение | расширение с точкой | .xml |
Путь | каталог с завершающим разделителем | C:\Обмен\ |
И методы, которые обращаются к диску:
- 📁
Существует()— есть ли файл или каталог по этому пути; - 📂
ЭтоКаталог()иЭтоФайл()— что именно лежит по пути; - 📏
Размер()— размер в байтах; - 🕒
ПолучитьВремяИзменения()— время изменения по часам компьютера, где выполняется код; - 🌐
ПолучитьУниверсальноеВремяИзменения()— то же время в UTC.
Свойства считаются из строки пути и работают даже для несуществующего файла. Методы идут на диск: для удалённого файла Размер() вызовет исключение.
Не склеивайте пути через "\" вручную. Код, который сегодня работает на Windows, завтра переедет на сервер под Linux. Разделитель возвращает ПолучитьРазделительПути(), а маску «все файлы» — ПолучитьМаскуВсеФайлы(). Для явного контекста есть варианты с суффиксами Клиента и Сервера.
Маска поддерживает подстановочные знаки * (любая последовательность символов) и ? (один символ). Несколько масок через точку с запятой функция не понимает: "*.xml;*.txt" ничего полезного не найдёт.
Как получить список файлов из каталога функцией НайтиФайлы
Базовый вызов принимает три параметра:
- Путь — каталог, в котором ищем.
- Маска — шаблон имени. По умолчанию — все файлы.
- ИскатьВПодкаталогах —
Истина, если нужно спуститься во вложенные папки. По умолчаниюЛожь.
НайтиФайлы возвращает и файлы, и подкаталоги, если их имя подходит под маску. Поэтому список почти всегда фильтруют по ЭтоФайл().
// Возвращает массив полных имён файлов каталога (без подкаталогов)
Функция ФайлыКаталога(Каталог, Маска = "") Экспорт
Если ПустаяСтрока(Маска) Тогда
Маска = ПолучитьМаскуВсеФайлы();
КонецЕсли;
Результат = Новый Массив;
Найденные = НайтиФайлы(Каталог, Маска, Ложь);
Для Каждого НайденныйФайл Из Найденные Цикл
Если НайденныйФайл.ЭтоФайл() Тогда
Результат.Добавить(НайденныйФайл.ПолноеИмя);
КонецЕсли;
КонецЦикла;
Возврат Результат;
КонецФункции
Вызов ФайлыКаталога("D:\Обмен\Входящие", "*.xml") вернёт все XML из папки. Порядок элементов в массиве платформа не гарантирует: он зависит от файловой системы. Если нужен порядок — сортируйте сами (пример ниже).
Нужно несколько расширений? Есть два пути:
- 🔁 вызвать
НайтиФайлыпо разу на каждую маску и объединить результаты; - 🔎 взять все файлы и проверить
НРег(НайденныйФайл.Расширение)по списку допустимых.
Второй вариант надёжнее на Linux: там файловая система различает регистр, и маска *.xml может пропустить DATA.XML.
Допустимые = Новый Соответствие;
Допустимые.Вставить(".xml", Истина);
Допустимые.Вставить(".json", Истина);
Для Каждого НайденныйФайл Из НайтиФайлы(Каталог, ПолучитьМаскуВсеФайлы(), Ложь) Цикл
Если НайденныйФайл.ЭтоФайл()
И Допустимые.Получить(НРег(НайденныйФайл.Расширение)) <> Неопределено Тогда
// обработка файла
КонецЕсли;
КонецЦикла;
Чтобы получить только каталоги, условие переворачивается: оставляйте элементы, у которых ЭтоКаталог() вернул Истина.
Рекурсивный обход вложенных каталогов
Самый короткий способ — передать третьим параметром Истина. НайтиФайлы(Каталог, ПолучитьМаскуВсеФайлы(), Истина) вернёт всё дерево одним массивом: файлы и папки вперемешку.
У такого вызова два минуса:
- ⚠️ нельзя ограничить глубину и пропустить ненужные ветки (
.git, архивы,node_modulesна общем ресурсе); - ⚠️ на большом дереве массив собирается целиком, а код ждёт, пока функция пройдёт всё.
Когда нужен контроль, обход пишут сами:
// Собирает файлы в Результат, спускаясь не глубже МаксГлубина
Процедура ОбойтиКаталог(Каталог, Результат, МаксГлубина = 5, Глубина = 0)
Элементы = НайтиФайлы(Каталог, ПолучитьМаскуВсеФайлы(), Ложь);
Для Каждого Элемент Из Элементы Цикл
Если Элемент.ЭтоКаталог() Тогда
Если Глубина < МаксГлубина
И Не СтрНачинаетсяС(Элемент.Имя, ".") Тогда
ОбойтиКаталог(Элемент.ПолноеИмя, Результат, МаксГлубина, Глубина + 1);
КонецЕсли;
Иначе
Результат.Добавить(Элемент);
КонецЕсли;
КонецЦикла;
КонецПроцедуры
// Вызов
ВсеФайлы = Новый Массив;
ОбойтиКаталог("D:\Архив", ВсеФайлы, 3);
Глубина 3 означает: корень и три уровня вложенности под ним. Папки, имя которых начинается с точки, пропускаются — так принято помечать служебные каталоги на Linux и в системах контроля версий. Уберите условие, если они вам нужны.
Собственный обход удобен ещё и тем, что ошибку доступа к одной папке можно перехватить, не теряя остальное дерево. Как это сделать — в разделе про ошибки.
Для дерева на десятки тысяч файлов не запускайте обход в интерактивном серверном вызове. Пользователь будет смотреть на «зависшую» форму. Вынесите обход в фоновое задание, а результат сохраните во временное хранилище.
Фильтрация файлов по дате изменения и размеру
НайтиФайлы умеет фильтровать только по маске имени. Дату и размер проверяют уже в цикле, обращаясь к методам объекта.
Пример: XML-файлы, изменённые за последние 7 дней и не больше 10 МБ.
ГраницаДаты = ТекущаяУниверсальнаяДата() - 7 * 86400; // 86 400 секунд в сутках
МаксРазмер = 10 * 1024 * 1024; // 10 485 760 байт
Подходящие = Новый Массив;
Для Каждого НайденныйФайл Из НайтиФайлы(Каталог, "*.xml", Ложь) Цикл
Если Не НайденныйФайл.ЭтоФайл() Тогда
Продолжить;
КонецЕсли;
Если НайденныйФайл.ПолучитьУниверсальноеВремяИзменения() >= ГраницаДаты
И НайденныйФайл.Размер() <= МаксРазмер Тогда
Подходящие.Добавить(НайденныйФайл);
КонецЕсли;
КонецЦикла;
Почему здесь UTC. ПолучитьВремяИзменения() отдаёт время по часам машины, где выполняется код. Если сравнить его с ТекущаяДатаСеанса(), а часовой пояс сеанса отличается от пояса сервера, граница уедет на несколько часов. Пара ПолучитьУниверсальноеВремяИзменения() и ТекущаяУниверсальнаяДата() от этого защищает.
Для сортировки сложите данные в таблицу значений:
Таблица = Новый ТаблицаЗначений;
Таблица.Колонки.Добавить("ПолноеИмя");
Таблица.Колонки.Добавить("Размер");
Таблица.Колонки.Добавить("Изменен");
Для Каждого НайденныйФайл Из Подходящие Цикл
Строка = Таблица.Добавить();
Строка.ПолноеИмя = НайденныйФайл.ПолноеИмя;
Строка.Размер = НайденныйФайл.Размер();
Строка.Изменен = НайденныйФайл.ПолучитьУниверсальноеВремяИзменения();
КонецЦикла;
Таблица.Сортировать("Изменен Убыв"); // самые свежие сверху
Каждый вызов Размер() и ПолучитьВремяИзменения() — отдельное обращение к диску. На сетевой папке с тысячами файлов это заметно. Сначала отсекайте лишнее маской, потом проверяйте дату и размер.
Ошибки и права доступа: как не уронить обработку
Что происходит в типовых ситуациях:
| Ситуация | Как ведёт себя код | Что делать |
|---|---|---|
| Каталога нет | пустой результат, причина не видна | проверить Новый Файл(Каталог).Существует() до поиска |
| Нет прав на каталог или его часть | исключение или неполный список | обернуть в Попытка, записать причину в журнал |
| Файл удалили между поиском и чтением | исключение в Размер() или при открытии | проверять Существует() и ловить исключение на каждом файле |
| Файл занят другой программой | исключение при чтении | пропустить, повторить позже |
Пустой массив — самая коварная ситуация. Обработка отработала «успешно», а файлы не загрузились, потому что путь написан с опечаткой. Проверяйте каталог явно:
КаталогОбъект = Новый Файл(Каталог);
Если Не КаталогОбъект.Существует() Или Не КаталогОбъект.ЭтоКаталог() Тогда
ВызватьИсключение СтрШаблон("Каталог %1 не найден или это не папка", Каталог);
КонецЕсли;
Попытка
Найденные = НайтиФайлы(Каталог, ПолучитьМаскуВсеФайлы(), Ложь);
Исключение
ЗаписьЖурналаРегистрации("Загрузка файлов",
УровеньЖурналаРегистрации.Ошибка, , ,
ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
Найденные = Новый Массив;
КонецПопытки;
В собственном рекурсивном обходе ставьте Попытка вокруг вызова для каждой папки. Тогда одна закрытая папка не обрушит весь проход.
Отдельная история — безопасный режим. Внешние обработки и расширения могут работать в безопасном режиме. Там обращение к файловой системе запрещено, и любой вызов НайтиФайлы даст исключение. Решения два: подключить обработку без безопасного режима (это решает администратор базы) или настроить профиль безопасности на кластере с разрешением на нужный каталог.
Получение файлов в веб-клиенте, тонком клиенте и на сервере
Главное правило: функция ищет файлы на той машине, где выполняется код. Директива компиляции определяет, чью файловую систему вы читаете.
| Где выполняется код | Чью папку видит | Чем искать |
|---|---|---|
&НаСервере, фоновое задание, регламентное задание | диски сервера 1С (в файловом варианте — компьютера, где запущен сеанс) | НайтиФайлы |
&НаКлиенте, тонкий клиент, синхронные вызовы разрешены | компьютер пользователя | НайтиФайлы |
&НаКлиенте, синхронные вызовы запрещены | компьютер пользователя | НайтиФайлыАсинх или НачатьПоискФайлов |
| Веб-клиент | компьютер пользователя, только с расширением работы с файлами | асинхронные методы после подключения расширения |
Серверный контекст
В клиент-серверном варианте серверный код выполняет рабочий процесс кластера. Он работает от имени учётной записи службы агента сервера 1С — по умолчанию это USR1CV8. Именно этой учётной записи нужны права на чтение папки, а не пользователю 1С.
Для сетевых ресурсов:
- Используйте UNC-путь
\\fileserver\exchange\in, а не букву диска. - Выдайте учётной записи службы права на общий ресурс и на папку.
- Если в кластере несколько рабочих серверов, путь должен быть доступен с каждого: серверный вызов может попасть на любой из них.
Объект Файл не передавайте с сервера на клиент. Возвращайте строки, структуры или массив структур с нужными полями.
Клиент и веб-клиент: асинхронный вариант
С платформы 8.3.18 асинхронный код пишут через Асинх и Ждать. Пример ниже работает и в тонком, и в веб-клиенте: сначала подключает расширение, потом даёт выбрать папку и ищет в ней файлы.
&НаКлиенте
Асинх Процедура ВыбратьКаталогИПоказатьФайлы(Команда)
Подключено = Ждать ПодключитьРасширениеРаботыСФайламиАсинх();
Если Не Подключено Тогда
Ждать УстановитьРасширениеРаботыСФайламиАсинх();
Подключено = Ждать ПодключитьРасширениеРаботыСФайламиАсинх();
Если Не Подключено Тогда
Сообщить("Без расширения работы с файлами список папки получить нельзя.");
Возврат;
КонецЕсли;
КонецЕсли;
Диалог = Новый ДиалогВыбораФайла(РежимДиалогаВыбораФайла.ВыборКаталога);
Выбранные = Ждать Диалог.ВыбратьАсинх();
Если Выбранные = Неопределено Тогда
Возврат; // пользователь закрыл диалог
КонецЕсли;
Найденные = Ждать НайтиФайлыАсинх(Выбранные[0], ПолучитьМаскуВсеФайлыКлиента(), Ложь);
Для Каждого НайденныйФайл Из Найденные Цикл
ЭтоКаталог = Ждать НайденныйФайл.ЭтоКаталогАсинх();
Если Не ЭтоКаталог Тогда
Сообщить(НайденныйФайл.ПолноеИмя);
КонецЕсли;
КонецЦикла;
КонецПроцедуры
В асинхронном коде методы объекта Файл, которые обращаются к диску, тоже вызывают в асинхронной форме: ЭтоКаталогАсинх(), РазмерАсинх(), ПолучитьВремяИзмененияАсинх(). Свойства вроде ПолноеИмя читаются как обычно.
На платформе ниже 8.3.18 та же логика пишется через НачатьПодключениеРасширенияРаботыСФайлами и НачатьПоискФайлов с объектом ОписаниеОповещения. Каждый шаг становится отдельной процедурой-обработчиком оповещения.
В веб-клиенте без расширения работы с файлами браузер не даст прочитать содержимое папки на компьютере пользователя. Если ставить расширение нельзя, остаётся передать файлы на сервер по выбору пользователя через НачатьПомещениеФайлов и дальше работать с ними там.
Клиент или сервер: что выбрать
- 🖥 Файлы лежат на общем ресурсе обмена — читайте на сервере или в регламентном задании. Не зависите от того, у кого какой клиент.
- 👤 Файлы на компьютере пользователя — только клиентский код, в веб-клиенте с расширением.
- 🔄 Нужно и найти на клиенте, и обработать на сервере — найдите файлы на клиенте, передайте их на сервер через помещение во временное хранилище и обрабатывайте там.
Частые ошибки в коде поиска файлов
- ❌ Перебор найденного без проверки
ЭтоФайл(). Папка с подходящим именем попадёт в список, и следующее чтение упадёт. - ❌ Жёсткая маска
"*.*". На Windows она находит все файлы, а на Linux пропустит файлы без точки в имени. БеритеПолучитьМаскуВсеФайлы(). - ❌ Сравнение времени изменения с
ТекущаяДатаСеанса(). Часовые пояса сеанса и сервера могут не совпадать. Сравнивайте UTC с UTC. - ❌ Удаление файлов прямо в цикле обхода того же каталога без копии списка. Сначала получите массив, затем обрабатывайте его. Повторный вызов
НайтиФайлыпосреди обработки даст другой набор. - ❌ Надежда на порядок массива. Нужна обработка по порядку — сортируйте по имени или дате явно.
- ❌ Один большой серверный вызов из формы. Длинный обход переносите в фоновое задание.