Поиск файлов и папок

MQL5 позволяет осуществлять поиск файлов и папок в пределах "песочниц" терминала, агентов тестирования и общей для всех терминалов (подробнее про "песочницы" см. вводную часть этой главы Работа с файлами). Если вы точно знаете требуемое имя и расположение файла/директории, используйте функцию FileIsExist.

long FileFindFirst(const string filter, string &found, int flag = 0)

Функция начинает поиск файлов и папок согласно переданному фильтру. Фильтр может содержать путь, состоящий из вложенных папок внутри "песочницы", и должен содержать точное имя или шаблон имени искомых элементов файловой системы. Параметр filter не может быть пустым.

Шаблоном является строка, в которой присутствует один или более подстановочных символов. Существует два типа таких символов: звездочка ('*') заменяет произвольное количество любых символов (включая и нулевое количество), а знак вопроса ('?') заменяет не более одного любого символа. Например, фильтр "*" найдет все файлы и папки, а "???.*" — только те из них, в которых имя не длиннее 3 символов, причем расширение может быть или не быть. Файлы с расширением "csv" можно найти фильтром "*.csv" (но учтите, что папка тоже может иметь расширение). Фильтр "*." находит элементы без расширения, а ".*" — элементы без имени. Однако здесь следует помнить один нюанс.

Во многих версиях Windows для элементов файловой системы генерируется два вида имен: длинное (по умолчанию, до 260 символов) и короткое (в формате 8.3, унаследованном от MS-DOS). Второй вид создается автоматически из длинного имени, если оно превышает 8 символов или расширение длиннее 3. Подобную генерацию коротких имен можно отключить в системе, если никакое программное обеспечение ими не пользуется, но обычно их поддержка включена.
 
Поиск файлов производится в обоих видах имен, из-за чего возвращаемый перечень может содержать неожиданные, на первый взгляд, элементы. В частности, короткое имя, если оно сгенерировано системой из длинного названия, всегда содержит начальную часть перед точкой, занимающую вплоть до 8 символов. В ней может случайно обнаружиться совпадение с искомым шаблоном.

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

Поиск производится только в конкретной папке (либо в корневой папке "песочницы", если в фильтре нет пути, либо в указанной вложенной папке, если фильтр содержит путь) и не заходит в подкаталоги.

Поиск не зависит от регистра. Например, запрос файлов "*.txt" выдаст также файлы с расширением "TXT", "Txt" и т.д.

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

В параметре found возвращается только имя и расширение без пути (иерархии папок), который мог быть указан в фильтре.

Если найденный элемент является папкой, справа к его имени добавляется символ '\' (обратная косая черта).

Параметр flag позволяет выбрать область поиска между локальной рабочей папкой текущей копии терминала (по значению 0) или общей папкой всех терминалов (по значению FILE_COMMON). Когда MQL-программа выполняется в тестере, её локальная "песочница" (0) находится в каталоге агента тестирования.

После завершения процедуры поиска полученный дескриптор следует освободить, вызвав FileFindClose (см. далее).

bool FileFindNext(long handle, string &found)

Функция продолжает поиск подходящих элементов файловой системы, начатый функцией FileFindFirst. Первым параметром передается дескриптор, полученный из FileFindFirst, за счет чего применяются все прежние условия поиска.

Если очередной элемент найден, его имя передается в вызывающий код через аргумент found, а функция возвращает true.

Если элементов больше нет, функция возвращает false.

void FileFindClose(long handle)

Функция закрывает дескриптор поиска, полученный в результате вызова FileFindFirst.

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

В качестве примера рассмотрим скрипт FileFind.mq5. В предыдущих разделах мы тестировали много других скриптов, создававших файлы в каталоге MQL5/Files/MQL5Book. Запросим перечень всех таких файлов.

void OnStart()
{
   string found// приемная переменная
   // начинаем поиск и получаем дескриптор
   long handle = PRTF(FileFindFirst("MQL5Book/*"found));
   if(handle != INVALID_HANDLE)
   {
      do
      {
         Print(found);
      }
      while(FileFindNext(handlefound));
      FileFindClose(handle);
   }
}

Даже если вы очистили этот каталог, в него можно скопировать поставляемые с книгой примеры файлов в различных кодировках. Таким образом, скрипт FileFind.mq5 должен будет вывести как минимум такой список (порядок перечисления может меняться):

ansi1252.txt
unicode1.txt
unicode2.txt
unicode3.txt
utf8.txt

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

bool DirList(const string filterstring &result[], bool common = false)
{
   string found[1];
   long handle = FileFindFirst(filterfound[0]);
   if(handle == INVALID_HANDLEreturn false;
   do
   {
      if(ArrayCopy(resultfoundArraySize(result)) != 1break;
   }
   while(FileFindNext(handlefound[0]));
   FileFindClose(handle);
   
   return true;
}

С помощью неё запросим перечень каталогов в локальной "песочнице". Для этого используем предположение, что каталоги обычно не имеют расширения (в принципе, это не всегда так, и потому более строгий запрос перечня вложенных папок желающим следует реализовать иначе). Фильтр для элементов без расширения, вообще говоря, таков: "*." (вы можете проверить его с помощью команды dir в оболочке Windows "dir *."). Однако в функциях MQL5 данный шаблон вызывает ошибку 5002 (WRONG_FILENAME). Поэтому укажем более "расплывчатый" шаблон "*.?": он означает элементы без расширения или с расширением в 1 символ.

void OnStart()
{
   ...
   string list[];
   // пробуем запросить элементы без расширения
   // (работает в командной строке Windows)
   PRTF(DirList("*."list)); // false / WRONG_FILENAME(5002)
   
   // расширим условие: расширение должно состоять из не более чем 1 символа
   if(DirList("*.?"list))
   {
      ArrayPrint(list);
      // пример: "MQL5Book\" "Tester\"
   }
}

На моей копии MetaTrader 5 скрипт находит 2 папки "MQL5Book\" и "Tester\" — первая из них должна быть и у вас, если вы запускали предыдущие тестовые скрипты.