при объединении в одном каталоге файлов с фотографиями от разных друзей и с разных устройств можно заметить, что расширения этих файлов различаются. Одни файлы формата JPEG имеют расширение .jpg, другие — .jpeg, а третьи — и вовсе .JPEG.
Некоторым людям нравится делать все расширения одинаковыми. Было бы полезно иметь возможность переименовать все файлы лишь одной командой. В то же время мы могли бы удалить все пробелы ' ' и заменить их, например, на '_'.
В данном примере мы реализуем такой инструмент и назовем его renamer. Он будет принимать диапазон входных шаблонов и их замен, это выглядит следующим образом:
$ renamer jpeg jpg JPEG jpg
В этом случае renamer рекурсивно проитерирует по текущему каталогу и выполнит поиск шаблонов jpeg и JPEG в именах всех файлов. Он заменит обе строки на jpg.
Как это делается
В этом примере мы реализуем инструмент, который рекурсивно просканирует все файлы внутри каталога и соотнесет их имена с заданным шаблоном. Все совпадения заменятся токенами, предоставленными пользователем, и найденные файлы будут переименованы соответствующим образом.
1. Сначала включим некоторые заголовочные файлы и объявим об использовании пространств имен std и filesystem:
#include <iostream>
#include <regex>
#include <vector>
#include <filesystem>
using namespace std;
using namespace filesystem;
2. Реализуем небольшую вспомогательную функцию, которая принимает путь к файлу в виде строки, а также диапазон пар для замены. Каждая пара для замены содержит шаблон и его замену. Проходя в цикле по диапазону, воспользуемся regex_replace, передавая ему входную строку и принимая преобразованную строку. После этого вернем полученную строку:
template <typename T>
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]
<< " <pattern> <replacement> ...n";
return 1;
}
4. Как только мы убедились, что получили во входных значениях необходимые пары, заполним ими вектор:
vector<pair<regex, string>> 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, но, подобно этим утилитам, не выводит размер файлов в каталогах.
Чтобы получить эквивалент размера каталога, нужно зайти во все подкаталоги и суммировать размеры всех файлов, содержащихся в них.
В данном примере мы реализуем инструмент, который делает именно это. Инструмент можно запустить для любого каталога, он определит размер всех его записей.
Как это делается
В этом примере мы реализуем приложение, которое итерирует по каталогу и перечисляет размеры файлов для каждой записи. Это просто для обычных файлов, но если мы смотрим на запись каталога, которая сама по себе является каталогом, то нужно заглянуть в него и суммировать размеры всех файлов, хранящихся в нем.
1. Сначала включим все необходимые заголовочные файлы и объявим об использовании пространств имен std и filesystem:
#include <iostream>
#include <sstream>
#include <iomanip>
#include <numeric>
#include <filesystem>
using namespace std;
using namespace filesystem;
2. Затем реализуем вспомогательную функцию, которая принимает в качестве аргумента directory_entry и возвращает его размер в файловой системе. Это не каталог, мы просто вернем размер файла, вычисленный с помощью file_size:
static size_t entry_size(const directory_entry &entry)
{
if (!is_directory(entry)) { return file_size(entry); }
3. Если это каталог, то нужно проитерировать по всем его записям и подсчитать их размер. Мы будем вызывать вспомогательную функцию entry_size рекурсивно при повторной встрече с подкаталогами:
return accumulate(directory_iterator{entry}, {}, 0u,