📚 Hub Books: Онлайн-чтение книгРазная литератураC++17 STL Стандартная библиотека шаблонов - Яцек Галовиц

C++17 STL Стандартная библиотека шаблонов - Яцек Галовиц

Шрифт:

-
+

Интервал:

-
+
1 ... 112 113 114 115 116 117 118 119 120 121
Перейти на страницу:
таких инструментов, как grep или awk. Пользователь мог просто ввести команду наподобие "grep -r foobar .", и инструмент рекурсивно прошел бы по текущему каталогу и нашел бы все файлы, содержащие строку "foobar".

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

Как это делается

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

1. Сначала включим все необходимые заголовочные файлы и объявим об использовании пространств имен std и filesystem:

#include <iostream>

#include <fstream>

#include <regex>

#include <vector>

#include <string>

#include <filesystem>

using namespace std;

using namespace filesystem;

2. Потом реализуем вспомогательную функцию. Она принимает путь к файлу и объект, содержащий регулярное выражение, описывающее искомый шаблон. Затем создаем экземпляр вектора, который будет включать пары, состоящие из номера строк и их содержимого. Кроме того, создадим экземпляр объекта файлового потока ввода, из которого будем считывать содержимое и сравнивать его с шаблоном строка за строкой:

static vector<pair<size_t, string>>

matches(const path &p, const regex &re)

{

  vector<pair<size_t, string>> d;

  ifstream is {p.c_str()};

3. Пройдем по файлу строка за строкой с помощью функции getline. Функция regex_search возвращает значение true при условии, что строка содержит наш шаблон. Если это именно так, то поместим в вектор номер строки и саму строку. Наконец, вернем все найденные совпадения:

  string s;

  for (size_t line {1}; getline(is, s); ++line) {

    if (regex_search(begin(s), end(s), re)) {

      d.emplace_back(line, move(s));

    }

  }

  return d;

}

4. В функции main сначала проверим, предоставил ли пользователь аргумент командной строки, который можно задействовать как шаблон. Если нет, то сгенерируем ошибку:

int main(int argc, char *argv[])

{

  if (argc != 2) {

    cout << "Usage: " << argv[0] << " <pattern>n";

    return 1;

}

5. Далее создадим объект регулярного выражения из входного шаблона. Невозможность создать такое регулярное выражение приведет к генерации исключения. При генерации исключения поймаем его и сгенерируем ошибку:

  regex pattern;

  try { pattern = regex{argv[1]}; }

  catch (const regex_error &e) {

    cout << "Invalid regular expression provided.n";

    return 1;

  }

6. Наконец, можно проитерировать по файловой системе и поискать совпадения с шаблоном. Воспользуемся итератором recursive_directory_iterator для итерации по всем файлам рабочего каталога. Он работает точно так же, как и итератор directory_iterator из предыдущего примера, но заходит еще и в подкаталоги. Таким образом, не нужно управлять рекурсией. Для каждой записи вызываем вспомогательную функцию matches:

  for (const auto &entry :

       recursive_directory_iterator{current_path()}) {

    auto ms (matches(entry.path(), pattern));

7. Для каждого совпадения (если они есть) выводим путь к файлу, номер строки и содержимое строки, содержащей совпадение:

    for (const auto &[number, content] : ms) {

      cout << entry.path().c_str() << ":" << number

           << " - " << content << 'n';

    }

  }

}

8. Подготовим файл с именем "foobar.txt", содержащий тестовые строки, по которым можно выполнить поиск:

foo

bar

baz

9. Компиляция и запуск программы дадут следующий результат. Я запустил приложение в каталоге /Users/tfc/testdir моего ноутбука и сначала передал ему шаблон "bar". Внутри этого каталога приложение нашло вторую строку в нашем файле "foobar.txt" и другом файле "text1.txt", который находится в каталоге testdir/dir1:

$ ./grepper bar

/Users/tfc/testdir/dir1/text1.txt:1 - foo bar bla blubb

/Users/tfc/testdir/foobar.txt:2 - bar

10. При повторном запуске приложения с шаблоном "baz" оно находит третью строку в нашем примере текстового файла:

$ ./grepper baz

/Users/tfc/testdir/foobar.txt:3 - baz

Как это работает

Создание и использование регулярного выражения с целью фильтрации содержимого файлов — основная цель данного примера. Однако рассмотрим итератор recursive_directory_iterator, поскольку этот особый класс итераторов мы применяли для фильтрации файлов, по которым итерируем рекурсивно.

Как и directory_iterator, recursive_directory_iterator итерирует по элементам каталога. Он делает это рекурсивно, согласно своему названию. При встрече с элементом файловой системы, который является каталогом, он вернет экземпляр типа directory_entry для данного пути, а затем зайдет в него, чтобы проитерировать по его потомкам.

Итератор recursive_directory_iterator имеет несколько интересных функций-членов.

□ depth() — говорит, на сколько уровней итератор спустился в подкаталоге.

□ recursion_pending() — сообщает, будет ли итератор спускаться дальше после элемента, на который он указывает в данный момент.

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

□ pop() — эта функция прерывает работу на текущем уровне и поднимает итератор на один уровень вверх в иерархии каталогов для продолжения работы.

Дополнительная информация

Еще одной важной деталью, о которой нужно знать, выступает класс-перечисление directory_options. Конструктор класса recursive_directory_iterator принимает значение этого типа в качестве второго аргумента. Значением по умолчанию, которое мы использовали неявно, является directory_options::none. Другие его значения выглядят следующим образом:

□ follow_directory_symlink — позволяет рекурсивному итератору следовать по символьным ссылкам на каталоги;

□ skip_permission_denied — указывает итератору пропускать каталоги, которые в противном случае вернут ошибку, поскольку файловая система не дает прав на доступ к ним.

Эти настройки можно объединять с помощью оператора |. 

Инструмент для автоматического переименования файлов

На создание этого примера меня сподвигла ситуация, в которую я попадаю довольно часто. Скажем,

1 ... 112 113 114 115 116 117 118 119 120 121
Перейти на страницу:

Комментарии

Обратите внимание, что комментарий должен быть не короче 20 символов. Покажите уважение к себе и другим пользователям!

Никто еще не прокомментировал. Хотите быть первым, кто выскажется?