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

          fs,

          is_regular_file(fs) ? file_size(entry.path()) : 0u};

}


3. Кроме того, понадобится вспомогательная функция

type_char
. Путь может представлять не только каталоги и простые текстовые/бинарные файлы. Операционные системы предоставляют множество разнообразных типов, которые абстрагируют что-то еще, например интерфейсы аппаратных устройств в виде так называемых символьных/блочных файлов. В библиотеке для работы с файловой системой, расположенной в STL, есть множество функций-предикатов для них. Таким образом, можно вернуть букву
'd'
для каталогов, букву
'f'
для обычных файлов и т.д.:


static char type_char(file_status fs)

{

  if  (is_directory(fs))          { return 'd'; }

  else if (is_symlink(fs))        { return 'l'; }

  else if (is_character_file(fs)) { return 'c'; }

  else if (is_block_file(fs))     { return 'b'; }

  else if (is_fifo(fs))           { return 'p'; }

  else if (is_socket(fs))         { return 's'; }

  else if (is_other(fs))          { return 'o'; }

  else if (is_regular_file(fs))   { return 'f'; }

  return '?';

}


4. Напишем еще одну вспомогательную функцию,

rwx
. Она принимает переменную
perms
(просто возвращающую тип класса перечисления из библиотеки для работы с файловой системой) и возвращает строку наподобие
"rwxrwxrwx"
, которая описывает настройки прав для файла. Первая группа символов
"rwx"
описывает права на чтение, запись и исполнение (read, write and execution) для владельца файла. Следующая группа описывает те же права для всех пользователей, являющихся частью пользовательской группы, к которой принадлежит файл. Последняя группа символов описывает эти же права для всех остальных. Строка
"rwxrwxrwx"
означает, что все пользователи могут получить доступ к объекту любым способом; строка
"rw-r--r--"
— что только владелец файла может читать и изменять его, а все остальные — только читать. Мы просто создадим строку на основе этих значений бит за битом. Лямбда-выражение поможет выполнить повторяющуюся работу, связанную с проверкой, содержит ли переменная
p
типа
perms
конкретный бит владельца, и возвратит символ
'-'
или соответствующую букву:


static string rwx(perms p)

{

  auto check ([p](perms bit, char c) {

    return (p & bit) == perms::none ? '-' : c;

  });

  return {check(perms::owner_read,   'r'),

          check(perms::owner_write,  'w'),

          check(perms::owner_exec,   'x'),

          check(perms::group_read,   'r'),

          check(perms::group_write,  'w'),

          check(perms::group_exec,   'x'),

          check(perms::others_read,  'r'),

          check(perms::others_write, 'w'),

          check(perms::others_exec,  'x')};

}


5. Наконец, последняя вспомогательная функция принимает целочисленный размер файла и преобразует его в читабельный вид. Мы просто проигнорируем точку при делении чисел и округлим их до ближайшего значения кило-, мега- или гигабайт.


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();

}


6. Теперь наконец можно реализовать функцию

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;

  }


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

file_info
из объектов
directory_entry
. Создадим
directory_iterator
и передадим в его конструктор объект пути, созданный на предыдущем шаге. При переборе с помощью итератора для каталогов преобразуем объекты типа
directory_entry
в кортежи, содержащие информацию о файлах, и вставляем их в вектор:


  vector> items;

  transform(directory_iterator{dir}, {},

  back_inserter(items), file_info);


8. Мы сохранили всю необходимую информацию в элементы вектора и можем просто сохранить ее с помощью написанных вспомогательных функций:


  for (const auto &[path, status, size] : items) {

    cout << type_char(status)

<< rwx(status.permissions()) << " "

<< setw(4) << right << size_string(size)

<< " " << path.filename().c_str()

<< '\n';

  }

}


9. Компиляция и запуск проекта с путем к офлайн-версии документации C++ дадут следующий результат. Мы видим, что папка содержит только другие каталоги и простые файлы, поскольку первыми символами каждой строки являются

'd'
и
'f'
. Данные файлы имеют разные права доступа и, конечно, различаются по размеру. Обратите внимание: файлы представлены в алфавитном порядке, но мы не можем полагаться на эту особенность, поскольку это не требуется в стандарте С++17.


$ ./list ~/Documents/cpp_reference/en/cpp

drwxrwxr-x   0B algorithm

frw-r--r--  88K algorithm.html

drwxrwxr-x   0B atomic

frw-r--r--  35K atomic.html

drwxrwxr-x   0B chrono

frw-r--r--  34K chrono.html

frw-r--r--  21K comment.html

frw-r--r--  21K comments.html

frw-r--r-- 220K compiler_support.html

drwxrwxr-x   0B concept

frw-r--r--  67K concept.html

drwxr-xr-x   0B container

frw-r--r-- 285K container.html

drwxrwxr-x   0B error

frw-r--r--  52K error.html


Как это работает

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

Чтобы обойти каталог, мы просто создали итератор

directory_iterator
и проитерировали с его помощью. Обход каталога фантастически легко совершить с помощью библиотеки
filesystem
:


for (const directory_entry &e : directory_iterator{dir}) {

  // сделать что-то

}