Итератор
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, но, подобно этим утилитам, не выводит размер файлов в