НайтиФайлы и обход папок: список файлов каталога во встроенном языке 1С

· 10 мин

Содержание

Чтобы программно получить файлы из каталога в 1С, вызовите глобальную функцию НайтиФайлы(Путь, Маска, ИскатьВПодкаталогах). Она возвращает массив объектов Файл: у каждого есть полное имя, расширение, размер и время изменения. В клиентском коде без синхронных вызовов и в веб-клиенте используйте асинхронный вариант НайтиФайлыАсинх (платформа 8.3.18 и новее) или НачатьПоискФайлов.

Дальше всё упирается в контекст. Код на сервере видит диски сервера, код на клиенте — диски компьютера пользователя. Веб-клиенту для работы с файловой системой нужно расширение работы с файлами. Ниже готовые фрагменты для каждого случая и разбор ошибок, на которых обычно спотыкаются.

Объект Файл и пути: с чем работает код

Работа с файловой системой во встроенном языке идёт через глобальные функции и объект Файл. НайтиФайлы создаёт такие объекты сама, но объект можно получить и вручную: Новый Файл("C:\Обмен\data.xml"). Сам файл при этом не открывается и не создаётся — объект просто описывает путь.

Свойства, которые нужны чаще всего:

СвойствоЧто содержитПример для C:\Обмен\data.xml
ПолноеИмяпуть целикомC:\Обмен\data.xml
Имяимя с расширениемdata.xml
ИмяБезРасширенияимя без расширенияdata
Расширениерасширение с точкой.xml
Путькаталог с завершающим разделителемC:\Обмен\

И методы, которые обращаются к диску:

  • 📁 Существует() — есть ли файл или каталог по этому пути;
  • 📂 ЭтоКаталог() и ЭтоФайл() — что именно лежит по пути;
  • 📏 Размер() — размер в байтах;
  • 🕒 ПолучитьВремяИзменения() — время изменения по часам компьютера, где выполняется код;
  • 🌐 ПолучитьУниверсальноеВремяИзменения() — то же время в UTC.

Свойства считаются из строки пути и работают даже для несуществующего файла. Методы идут на диск: для удалённого файла Размер() вызовет исключение.

Не склеивайте пути через "\" вручную. Код, который сегодня работает на Windows, завтра переедет на сервер под Linux. Разделитель возвращает ПолучитьРазделительПути(), а маску «все файлы» — ПолучитьМаскуВсеФайлы(). Для явного контекста есть варианты с суффиксами Клиента и Сервера.

Маска поддерживает подстановочные знаки * (любая последовательность символов) и ? (один символ). Несколько масок через точку с запятой функция не понимает: "*.xml;*.txt" ничего полезного не найдёт.

Как получить список файлов из каталога функцией НайтиФайлы

Базовый вызов принимает три параметра:

  1. Путь — каталог, в котором ищем.
  2. Маска — шаблон имени. По умолчанию — все файлы.
  3. ИскатьВПодкаталогах — Истина, если нужно спуститься во вложенные папки. По умолчанию Ложь.

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

// Возвращает массив полных имён файлов каталога (без подкаталогов)
Функция ФайлыКаталога(Каталог, Маска = "") Экспорт

	Если ПустаяСтрока(Маска) Тогда
		Маска = ПолучитьМаскуВсеФайлы();
	КонецЕсли;

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

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

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

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

Вызов ФайлыКаталога("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С.

Для сетевых ресурсов:

  1. Используйте UNC-путь \\fileserver\exchange\in, а не букву диска.
  2. Выдайте учётной записи службы права на общий ресурс и на папку.
  3. Если в кластере несколько рабочих серверов, путь должен быть доступен с каждого: серверный вызов может попасть на любой из них.

Объект Файл не передавайте с сервера на клиент. Возвращайте строки, структуры или массив структур с нужными полями.

Клиент и веб-клиент: асинхронный вариант

С платформы 8.3.18 асинхронный код пишут через Асинх и Ждать. Пример ниже работает и в тонком, и в веб-клиенте: сначала подключает расширение, потом даёт выбрать папку и ищет в ней файлы.

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

	Подключено = Ждать ПодключитьРасширениеРаботыСФайламиАсинх();
	Если Не Подключено Тогда
		Ждать УстановитьРасширениеРаботыСФайламиАсинх();
		Подключено = Ждать ПодключитьРасширениеРаботыСФайламиАсинх();
		Если Не Подключено Тогда
			Сообщить("Без расширения работы с файлами список папки получить нельзя.");
			Возврат;
		КонецЕсли;
	КонецЕсли;

	Диалог = Новый ДиалогВыбораФайла(РежимДиалогаВыбораФайла.ВыборКаталога);
	Выбранные = Ждать Диалог.ВыбратьАсинх();
	Если Выбранные = Неопределено Тогда
		Возврат; // пользователь закрыл диалог
	КонецЕсли;

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

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

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

На платформе ниже 8.3.18 та же логика пишется через НачатьПодключениеРасширенияРаботыСФайлами и НачатьПоискФайлов с объектом ОписаниеОповещения. Каждый шаг становится отдельной процедурой-обработчиком оповещения.

В веб-клиенте без расширения работы с файлами браузер не даст прочитать содержимое папки на компьютере пользователя. Если ставить расширение нельзя, остаётся передать файлы на сервер по выбору пользователя через НачатьПомещениеФайлов и дальше работать с ними там.

Клиент или сервер: что выбрать

  • 🖥 Файлы лежат на общем ресурсе обмена — читайте на сервере или в регламентном задании. Не зависите от того, у кого какой клиент.
  • 👤 Файлы на компьютере пользователя — только клиентский код, в веб-клиенте с расширением.
  • 🔄 Нужно и найти на клиенте, и обработать на сервере — найдите файлы на клиенте, передайте их на сервер через помещение во временное хранилище и обрабатывайте там.

Частые ошибки в коде поиска файлов

  • ❌ Перебор найденного без проверки ЭтоФайл(). Папка с подходящим именем попадёт в список, и следующее чтение упадёт.
  • ❌ Жёсткая маска "*.*". На Windows она находит все файлы, а на Linux пропустит файлы без точки в имени. Берите ПолучитьМаскуВсеФайлы().
  • ❌ Сравнение времени изменения с ТекущаяДатаСеанса(). Часовые пояса сеанса и сервера могут не совпадать. Сравнивайте UTC с UTC.
  • ❌ Удаление файлов прямо в цикле обхода того же каталога без копии списка. Сначала получите массив, затем обрабатывайте его. Повторный вызов НайтиФайлы посреди обработки даст другой набор.
  • ❌ Надежда на порядок массива. Нужна обработка по порядку — сортируйте по имени или дате явно.
  • ❌ Один большой серверный вызов из формы. Длинный обход переносите в фоновое задание.

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

Как посчитать количество файлов в папке?
Получите массив через НайтиФайлы и отфильтруйте по ЭтоФайл(). Количество элементов итогового массива и есть число файлов. Отдельной функции подсчёта в языке нет.
Можно ли найти файл по части имени?
Да. Маска поддерживает подстановочные знаки: «отчет» найдёт все файлы, в имени которых есть «отчет». Для сложных условий берите все файлы и проверяйте имя через СтрНайти или СтрНачинаетсяС.
Как получить имя файла без пути из полного пути?
Создайте Новый Файл(ПолныйПуть) и прочитайте свойство Имя или ИмяБезРасширения. Обращения к диску при этом не будет, файл может и не существовать.
Работает ли НайтиФайлы в регламентном задании?
Да, это серверный контекст. Путь указывается относительно сервера 1С, а права нужны учётной записи службы. В файловом варианте регламентные задания выполняются в сеансе одного из пользователей.
Почему в файловой базе всё работало, а после перехода на клиент-сервер файлы не находятся?
В файловом варианте серверный код выполняется на компьютере пользователя и видит его диски. В клиент-серверном он выполняется на сервере 1С. Путь C:\Обмен теперь означает папку на сервере.