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

каталогах.

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

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


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

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


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

std
и
filesystem
:


#include 

#include 

#include 

#include 

#include 


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,

        [](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 

#include 

#include 

#include 

#include 


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> ext_stats(const path &dir)

{

  map> m;

  for (const auto &entry :

      recursive_directory_iterator{dir}) {


4. Если запись каталога тоже является каталогом, то ее можно опустить. Это не значит, что мы не будем рекурсивно спускаться в каталог. Итератор

recursive_ directory_iterator