C++17 STL Стандартная библиотека шаблонов — страница 116 из 119

Итератор

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

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

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

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

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


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

Еще одной важной деталью, о которой нужно знать, выступает класс-перечисление

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

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

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


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

|

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

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

.jpg
, другие —
.jpeg
, а третьи — и вовсе
.JPEG
.

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

' '
и заменить их, например, на
'_'
.

В данном примере мы реализуем такой инструмент и назовем его

renamer
. Он будет принимать диапазон входных шаблонов и их замен, это выглядит следующим образом:


$ renamer jpeg jpg JPEG jpg


В этом случае

renamer
рекурсивно проитерирует по текущему каталогу и выполнит поиск шаблонов
jpeg
и
JPEG
в именах всех файлов. Он заменит обе строки на
jpg
.


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

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


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

std
и
filesystem
:


#include 

#include 

#include 

#include 


using namespace std;

using namespace filesystem;


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

regex_replace
, передавая ему входную строку и принимая преобразованную строку. После этого вернем полученную строку:


template 

static string replace(string s, const T &replacements)

{

  for (const auto &[pattern, repl] : replacements) {

    s = regex_replace(s, pattern, repl);

  }

  return s;

}


3. В функции

main
сначала проверим командную строку. Принимаем аргументы из нее попарно, поскольку нужно, чтобы шаблоны стояли рядом с их заменами. Первый элемент
argv
— всегда имя исполняемого файла. Это значит, что если пользователь предоставит хотя бы одну пару или больше, то
argc
должен быть нечетным и не меньше
3
:


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

{

  if (argc < 3 || argc % 2 != 1) {

    cout << "Usage: " << argv[0]

<< "  ...\n";

    return 1;

  }


4. Как только мы убедились, что получили во входных значениях необходимые пары, заполним ими вектор:


  vector> patterns;

  for (int i {1}; i < argc; i += 2) {

    patterns.emplace_back(argv[i], argv[i + 1]);

}


5. Теперь можно проитерировать по файловой системе. Для простоты определим в качестве каталога, по которому нужно проитерировать, текущий каталог приложения. Затем возьмем только имена файлов без остальной части пути и преобразуем их соответственно списку шаблонов и замен, собранному ранее. Возьмем копию

opath
, назовем ее
rpath
и заменим часть имени файла новой:


for (const auto &entry :

    recursive_directory_iterator{current_path()}) {

  path opath {entry.path()};

  string rname {replace(opath.filename().string(),

                patterns)};

  path rpath {opath};

  rpath.replace_filename(rname);


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


    if (opath != rpath) {

      cout << opath.c_str() << " --> "

<< rpath.filename().c_str() << '\n';

      if (exists(rpath)) {

        cout << "Error: Can't rename."

                " Destination file exists.\n";

      } else {

        rename(opath, rpath);

      }

    }

  }

}


7. Компиляция и запуск программы для примера каталога дадут следующий результат. Я поместил несколько картинок в формате JPEG в каталог, но задал для них разные окончания:

jpg
,
jpeg
и
JPEG
. Затем выполнил программу, передав ей шаблоны
jpeg
и
JPEG
и выбрав замену
jpg
для них обоих. В результате получил каталог с одинаковыми расширениями файлов:


$ ls

birthday_party.jpeg holiday_in_dubai.jpg holiday_in_spain.jpg

trip_to_new_york.JPEG

$ ../renamer jpeg jpg JPEG jpg

/Users/tfc/pictures/birthday_party.jpeg --> birthday_party.jpg

/Users/tfc/pictures/trip_to_new_york.JPEG --> trip_to_new_york.jpg

$ ls

birthday_party.jpg holiday_in_dubai.jpg holiday_in_spain.jpg

trip_to_new_york.jpg

Создаем индикатор эксплуатации диска

Мы уже реализовали инструмент, который работает как

ls
в Linux/MacOS или dir в Windows, но, подобно этим утилитам, не выводит размер файлов в