Как найти файлы по нескольким маскам с помощью функции НайтиФайлы в 1С?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При разработке механизмов интеграции, выгрузки данных или разбора логов перед программистом 1С часто возникает задача поиска файлов определенного типа. В процессе работы многие сталкиваются с неприятным ограничением платформы: попытка указать несколько масок через запятую или точку с запятой (например, *module.bsl,form.xml) в функции НайтиФайлы() возвращает пустой массив. При этом популярные файловые менеджеры, такие как FAR Manager или Total Commander, без проблем справляются с подобными запросами.

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

Выясняем причину: почему маски не разделяются «из коробки»

Для начала давайте разберемся с технической первопричиной проблемы. Функция НайтиФайлы() во встроенном языке «1С:Предприятие» не является самостоятельным поисковым движком. Она представляет собой высокоуровневую обертку над системными вызовами операционной системы, в которой запущено приложение (клиент или сервер).

В операционной системе Windows для поиска файлов на низком уровне используется функция WinAPI FindFirstFile (а также её спутник FindNextFile). Данный API на уровне ядра ОС принципиально поддерживает работу только с одним шаблоном поиска за один раз. Он не умеет самостоятельно парсить строки, разделенные запятыми, точками с запятой или другими разделителями.

Файловые менеджеры создают иллюзию работы с множественными масками исключительно за счет собственного программного кода. Получая строку вида *.txt;*.xml, программа предварительно разбивает её на массив подстрок, а затем запускает цикл поиска по каждой маске отдельно, после чего объединяет результаты в один список. Встроенный язык 1С не делает этого автоматически, оставляя данную логику на усмотрение разработчика.

Рассмотрим, как правильно решить эту задачу в коде 1С, избежав типичных ошибок производительности и кроссплатформенности.

Вариант 1. Последовательный обход массива масок (Рекомендуемый подход)

Самым производительным и правильным с точки зрения потребления ресурсов решением является запуск поиска в цикле по каждой маске отдельно с последующим объединением полученных массивов. Этот способ минимизирует объем оперативной памяти, так как платформа запрашивает у операционной системы только те файлы, которые гарантированно соответствуют условиям поиска.

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


Функция НайтиФайлыПоНесколькимМаскам(Знач ПутьККаталогу, Знач МассивМасок, Знач ИскатьВПодкаталогах = Ложь) Экспорт

    ОбщийРезультат = Новый Массив;
    
    // Используем Соответствие для исключения дублирования файлов, 
    // если маски теоретически могут пересекаться
    УникальныеФайлы = Новый Соответствие;
    
    Для Каждого ТекущаяМаска Из МассивМасок Цикл
        
        // Очищаем маску от возможных пробелов по краям
        ОчищеннаяМаска = СокрЛП(ТекущаяМаска);
        Если ПустаяСтрока(ОчищеннаяМаска) Тогда
            Продолжить;
        КонецЕсли;
        
        // Выполняем поиск по текущей маске
        НайденныеФайлы = НайтиФайлы(ПутьККаталогу, ОчищеннаяМаска, ИскатьВПодкаталогах);
        
        Для Каждого Файл Из НайденныеФайлы Цикл
            // В качестве ключа используем полное имя файла для обеспечения уникальности
            Если УникальныеФайлы[Файл.ПолноеИмя] = Неопределено Тогда
                УникальныеФайлы.Вставить(Файл.ПолноеИмя, Файл);
                ОбщийРезультат.Добавить(Файл);
            КонецЕсли;
        КонецЦикла;
        
    КонецЦикла;
    
    Возврат ОбщийРезультат;

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

Преимущества этого подхода:

  1. Высокая скорость работы. Операционная система возвращает только отфильтрованные данные. Платформе 1С не приходится тратить процессорное время на инициализацию "тяжелых" объектов типа Файл для ненужных объектов файловой системы.
  2. Экономия памяти. В оперативной памяти создаются объекты только для тех файлов, которые соответствуют целевым маскам.
  3. Уникальность результатов. Использование структуры Соответствие предотвращает появление дублей, если одна из масок частично перекрывает другую (например, *.xml и form.xml).

Вариант 2. Поиск всех файлов с последующей фильтрацией (Кроссплатформенные нюансы)

Некоторые разработчики идут по пути наименьшего сопротивления: они ищут абсолютно все файлы в каталоге с помощью универсальной маски, а затем программно отсеивают лишние элементы, анализируя расширения. Этот способ допустим, только если вы твердо уверены, что в целевой папке находится небольшое количество файлов (не более нескольких сотен). Если же файлов в папке десятки тысяч, этот метод приведет к колоссальным задержкам и избыточному расходу памяти сервера.

Если вы все же решили использовать этот метод, важно обойти серьезную кроссплатформенную ловушку, связанную с отличиями работы Windows и Linux.

Многие привыкли использовать маску *.* для поиска всех файлов. Однако в операционных системах семейства Linux маска *.* вернет только те файлы, в имени которых присутствует хотя бы одна точка. Файлы без расширения (например, системные файлы или конфигурационные скрипты) под эту маску не попадут. В Windows же маска *.* исторически возвращает абсолютно все файлы.

Чтобы ваш код работал одинаково на любой операционной системе (что критически важно для серверного кода, так как современные серверы 1С часто работают под управлением Linux), необходимо использовать специализированные системные функции платформы для получения универсальной маски всех файлов:

Посмотрим на пример реализации фильтрации в памяти:


Функция НайтиИОтфильтроватьФайлы(Знач ПутьККаталогу, Знач МассивРасширений) Экспорт

    Результат = Новый Массив;
    
    // Получаем корректную маску для всех файлов под текущую ОС
    #Если Сервер Или ТолстыйКлиентОбычноеПриложение Или ВнешнееСоединение Тогда
        МаскаВсехФайлов = ПолучитьМаскуВсеФайлыСервера();
    #Иначе
        МаскаВсехФайлов = ПолучитьМаскуВсеФайлы();
    #КонецЕсли
    
    ВсеФайлы = НайтиФайлы(ПутьККаталогу, МаскаВсехФайлов);
    
    Для Каждого Файл Из ВсеФайлы Цикл
        
        // Пропускаем каталоги, если нас интересуют только файлы
        Если Файл.ЭтоКаталог() Тогда
            Продолжить;
        КонецЕсли;
        
        // Получаем расширение файла без точки и переводим в нижний регистр
        РасширениеФайла = НРег(Сред(Файл.Расширение, 2));
        
        Если МассивРасширений.Найти(РасширениеФайла) <> Неопределено Тогда
            Результат.Добавить(Файл);
        КонецЕсли;
        
    КонецЦикла;
    
    Возврат Результат;

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

Вариант 3. Современный асинхронный поиск файлов (Для управляемого клиента)

В современных конфигурациях 1С:Предприятие 8.3, разработанных на базе Библиотеки Стандартных Подсистем (БСП), в свойствах конфигурации практически всегда включен режим «Запретить синхронные вызовы». Это означает, что попытка вызвать функцию НайтиФайлы() в коде клиентской части (в тонком или веб-клиенте) приведет к аварийному завершению работы программы.

Для работы на клиенте мы обязаны использовать асинхронный метод платформы НачатьПоискФайлов(), который работает через механизм описания оповещений (callback). Давайте разберем, как реализовать асинхронный поиск по нескольким маскам.

Для этого нам понадобится рекурсивный вызов асинхронной процедуры для последовательного обхода всего массива масок:


// Основная процедура запуска асинхронного поиска
Процедура ЗапуститьАсинхронныйПоиск(ПутьКПапке, МассивМасок) Экспорт
    
    ПараметрыПоиска = Новый Структура;
    ПараметрыПоиска.Вставить("ПутьКПапке", ПутьКПапке);
    ПараметрыПоиска.Вставить("МассивМасок", МассивМасок);
    ПараметрыПоиска.Вставить("ИндексТекущейМаски", 0);
    ПараметрыПоиска.Вставить("ОбщийРезультат", Новый Массив);
    ПараметрыПоиска.Вставить("УникальныеИмена", Новый Соответствие);
    
    ВыполнитьШагПоиска(ПараметрыПоиска);
    
КонецПроцедуры

// Процедура, выполняющая очередной шаг поиска
Процедура ВыполнитьШагПоиска(ПараметрыПоиска)
    
    МассивМасок = ПараметрыПоиска.МассивМасок;
    Индекс = ПараметрыПоиска.ИндексТекущейМаски;
    
    // Если обошли все маски, переходим к обработке результата
    Если Индекс >= МассивМасок.Количество() Тогда
        ОбработатьРезультатыПоиска(ПараметрыПоиска.ОбщийРезультат);
        Возврат;
    КонецЕсли;
    
    ТекущаяМаска = МассивМасок[Индекс];
    
    // Описание оповещения, которое сработает после завершения сканирования диска платформой
    ОписаниеОповещения = Новый ОписаниеОповещения("ПослеПоискаОчереднойМаски", ЭтотОбъект, ПараметрыПоиска);
    
    НачатьПоискФайлов(ОписаниеОповещения, ПараметрыПоиска.ПутьКПапке, ТекущаяМаска, Ложь);
    
КонецПроцедуры

// Обработчик завершения поиска по конкретной маске
Процедура ПослеПоискаОчереднойМаски(НайденныеФайлы, ДополнительныеПараметры) Экспорт
    
    ОбщийРезультат = ДополнительныеПараметры.ОбщийРезультат;
    УникальныеИмена = ДополнительныеПараметры.УникальныеИмена;
    
    Для Каждого Файл Из НайденныеФайлы Цикл
        Если УникальныеИмена[Файл.ПолноеИмя] = Неопределено Тогда
            УникальныеИмена.Вставить(Файл.ПолноеИмя, Истина);
            ОбщийРезультат.Добавить(Файл);
        КонецЕсли;
    КонецЦикла;
    
    // Переходим к следующей маске
    ДополнительныеПараметры.ИндексТекущейМаски = ДополнительныеПараметры.ИндексТекущейМаски + 1;
    ВыполнитьШагПоиска(ДополнительныеПараметры);
    
КонецПроцедуры

// Финальный метод обработки собранного массива файлов
Процедура ОбработатьРезультатыПоиска(МассивФайлов)
    
    ПоказатьПредупреждение(, "Поиск завершен. Найдено файлов: " + МассивФайлов.Количество());
    // Здесь располагается ваша прикладная логика обработки файлов
    
КонецПроцедуры

Важная деталь: Не путайте маску поиска с фильтром диалога выбора файлов

Очень часто начинающие разработчики путают синтаксис функции НайтиФайлы() со свойством Фильтр объекта ДиалогВыбораФайла.

В интерактивном диалоге выбора файлов действительно можно (и нужно) использовать перечисление расширений через точку с запятой, например: "Текстовые файлы (*.txt;*.log)|*.txt;*.log". Однако этот фильтр обрабатывается графической оболочкой операционной системы (Проводником Windows или диалоговым окном среды рабочего стола Linux) исключительно для визуальной фильтрации файлов в пользовательском окне. Программная функция НайтиФайлы() такой синтаксис не поддерживает и требует строго одну маску на один вызов.

Используя приведенные выше паттерны и функции, вы сможете создавать надежные, кроссплатформенные и быстродействующие механизмы работы с файловой системой в любых конфигурациях «1С:Предприятие 8».

← На главную