class="code"> [](size_t accum, const directory_entry &e) {
return accum + entry_size(e);
});
}
4. Для повышения читабельности воспользуемся функцией size_string, которая уже встречалась в этой главе. Она просто сокращает большие размеры файлов, делая их «аккуратнее» и добавляя префиксы «кило», «мега» или «гига»:
static string size_string(size_t size)
{
stringstream ss;
if (size >= 1000000000) {
ss << (size / 1000000000) << 'G';
} else if (size >= 1000000) {
ss << (size / 1000000) << 'M';
} else if (size >= 1000) {
ss << (size / 1000) << 'K';
} else { ss << size << 'B'; }
return ss.str();
}
5. Первое, что нужно сделать в функции main, — проверить, предоставил ли пользователь путь к файлу в командной строке. Если это не так, то возьмем текущий каталог. Прежде чем продолжить, проверим, существует ли данный каталог:
int main(int argc, char *argv[])
{
path dir {argc > 1 ? argv[1] : "."};
if (!exists(dir)) {
cout << "Path " << dir << " does not exist.n";
return 1;
}
6. Теперь можно проитерировать по всем записям каталога и вывести на экран их имена и размер:
for (const auto &entry : directory_iterator{dir}) {
cout << setw(5) << right
<< size_string(entry_size(entry))
<< " " << entry.path().filename().c_str()
<< 'n';
}
}
7. Компиляция и запуск программы дадут следующий результат. Я запустил ее для каталога, в котором находится офлайн-справка по С++. Поскольку он содержит и подкаталоги, наша вспомогательная функция, суммирующая размер файла, очень пригодится:
$ ./file_size ~/Documents/cpp_reference/en/
19M c
12K c.html
147M cpp
17K cpp.html 22K index.html
22K Main_Page.html
Как это работает
Вся программа строится на использовании функции file_size для обычных файлов. Если программа увидит каталог, то рекурсивно спустится в него и вызовет функцию file_size для всех его записей.
Единственное, что мы сделали для определения того, можем ли вызвать непосредственно file_size или нужно рекурсивно спуститься дальше, — реализовали предикат is_directory. Он работает для каталогов, которые содержат только обычные файлы и каталоги.
Поскольку наша программа довольно проста, она даст сбой при следующих условиях.
□ Функция file_size работает только для обычных файлов и символьных ссылок. Она генерирует исключение во всех других случаях.
□ Несмотря на то что функция file_size работает для символьных ссылок, она все еще сгенерирует исключение, если мы вызовем ее для неработающей символьной ссылки.
Чтобы сделать программу из нашего примера более надежной, следует воспользоваться защитным программированием в случаях неверных типов файлов и обработки исключений.
Подбиваем статистику о типах файлов
В предыдущем примере мы реализовали инструмент, который выводит на экран размер всех членов каталога.
В текущем примере тоже будем определять размер рекурсивно, но в этот раз объединим размеры всех файлов с одинаковым расширением. Таким образом, сможем вывести для пользователя таблицу, в которой перечисляется количество и тип файлов, а также средний размер файлов каждого типа.
Как это делается
В этом примере мы реализуем небольшой инструмент, который рекурсивно итерирует по заданному каталогу. В процессе он определяет, файлы с какими расширениями находятся в данном каталоге, сколько файлов присутствует для каждого расширения, а также их средний размер.
1. Сначала включим необходимые заголовочные файлы и объявим об использовании пространств имен std и filesystem:
#include <iostream>
#include <sstream>
#include <iomanip>
#include <map>
#include <filesystem>
using namespace std;
using namespace filesystem;
2. Функция size_string была полезна в предыдущих примерах. Она преобразует размеры файлов в читабельные строки:
static string size_string(size_t size)
{
stringstream ss;
if (size >= 1000000000) {
ss << (size / 1000000000) << 'G';
} else if (size >= 1000000) {
ss << (size / 1000000) << 'M';
} else if (size >= 1000) {
ss << (size / 1000) << 'K';
} else { ss << size << 'B'; }
return ss.str();
}
3. Затем реализуем вспомогательную функцию, которая принимает объект пути и итерирует по всем файлам данного пути. При этом она собирает всю информацию в ассоциативный массив, в котором соотносятся расширения файлов и пары, содержащие общее количество файлов и их суммарный размер.
static map<string, pair<size_t, size_t>> ext_stats(const path &dir)
{
map<string, pair<size_t, size_t>> m;
for (const auto &entry :
recursive_directory_iterator{dir}) {
4. Если запись каталога тоже является каталогом, то ее можно опустить. Это не значит, что мы не будем рекурсивно спускаться в каталог. Итератор recursive_ directory_iterator все равно совершит данное действие, но мы не хотим сами заходить в эти подкаталоги.
const path p {entry.path()};
const file_status fs {status(p)};
if (is_directory(fs)) { continue; }
5. Далее извлекаем расширения из строки, представляющей запись каталога. Если у нее нет расширения, то просто опускаем ее:
const string ext {p.extension().string()};
if (ext.length() == 0) { continue; }
6. Подсчитаем размер текущего файла. Затем найдем агрегатный объект в ассоциативном массиве для этого расширения. Если такого объекта нет, то неявно создадим его. Просто увеличим счетчик количества файлов и добавим размер файла в переменную-аккумулятор:
const size_t size {file_size(p)};
auto &[size_accum, count] = m[ext];
size_accum += size;
count += 1;
}
7. После этого вернем ассоциативный массив:
return m;
}
8. В функции main примем путь, предоставленный пользователем в командной строке. Конечно, нужно проверить, существует ли он, поскольку в противном случае продолжать не имеет смысла.
int main(int argc, char *argv[])
{
path dir {argc > 1 ? argv[1] : "."};
if (!exists(dir)) {
cout << "Path " << dir << " does not exist.n";
return 1;
}
9. Можно мгновенно проитерировать