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}) {
// сделать что-то
}