Правда заключается в том, что это всего лишь класс итератора, но функции std::begin
std::end
предоставляют перегруженные версии данного типа. Данное обстоятельство позволяет вызвать функции begin
и end
для подобного итератора, и они снова будут возвращать итераторы. Это может показаться странным на первый взгляд, но делает наш класс более полезным. Инструмент текстового поиска в стиле grep
Большая часть операционных систем оснащена неким локальным инструментом поиска. Пользователи могут запустить его нажатием сочетания клавиш, а затем ввести имя локального файла, который хотели бы найти.
Прежде чем появилась подобная возможность, пользователи командной строки выполняли поиск файлов с помощью таких инструментов, как grep
awk
. Пользователь мог просто ввести команду наподобие "grep -r foobar ."
, и инструмент рекурсивно прошел бы по текущему каталогу и нашел бы все файлы, содержащие строку "foobar"
.В этом примере мы реализуем точно такое же приложение. Наш небольшой клон grep
Как это делается
В этом разделе мы реализуем небольшой инструмент, который выполняет поиск предоставленных пользователем текстовых шаблонов в файлах. Инструмент работает точно так же, как и инструмент UNIX grep
1. Сначала включим все необходимые заголовочные файлы и объявим об использовании пространств имен std
filesystem
:#include
#include
#include
#include
#include
#include
using namespace std;
using namespace filesystem;
2. Потом реализуем вспомогательную функцию. Она принимает путь к файлу и объект, содержащий регулярное выражение, описывающее искомый шаблон. Затем создаем экземпляр вектора, который будет включать пары, состоящие из номера строк и их содержимого. Кроме того, создадим экземпляр объекта файлового потока ввода, из которого будем считывать содержимое и сравнивать его с шаблоном строка за строкой:
static vector
matches(const path &p, const regex &re)
{
vector
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] << "
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