<< path{"testdir"} / "foobar.txt" << 'n';
5. Рассмотрим работу с функцией canonical и составными путями. Передавая функции canonical относительный путь к файлу, например "foobar.txt", и составной абсолютный путь current_path()/"testdir", получаем существующий абсолютный путь к файлу. В следующем обращении к функции передаем наш путь p (т.е. "testdir/foobar.txt") и абсолютный путь current_path(), который направляет нас в каталог "testdir" и обратно. Это то же самое, что и current_path(), из-за косвенных адресов. В обоих вызовах функция canonical должна возвратить одинаковый абсолютный путь.
cout << "canonical testdir : "
<< canonical("foobar.txt",
current_path()/"testdir")
<< "ncanonical testdir 2 : "
<< canonical(p, current_path()/"testdir/..")
<< 'n';
6. Кроме того, можно проверить эквивалентность двух путей, не являющихся каноническими. Функция equivalence приводит к каноническому виду пути к файлам, которые она принимает в качестве аргументов, и в конечном итоге возвращает значение true при условии, что они описывают один и тот же путь. Для этой проверки путь к файлу должен действительно существовать, в противном случае функция сгенерирует исключение.
cout << "equivalence: "
<< equivalent("testdir/foobar.txt",
"testdir/../testdir/foobar.txt")
<< 'n';
}
7. Компиляция и запуск программы дадут следующий результат. Функция current_path() возвращается к домашнему каталогу на моем ноутбуке, поскольку я запустил приложение оттуда. К нашему относительному пути p был добавлен префикс, состоящий из данной папки, с помощью функций absolute_path, system_complete и canonical. Мы видим, что функции absolute_path и system_complete выдают абсолютно одинаковое описание пути файла в моей системе, потому что это Mac (на Linux будет так же). В компьютере с операционной системой Windows функция system_complete добавит префикс "C:" или любого другого диска, в котором расположен рабочий каталог.
$ ./canonical_filepath
current_path : "/Users/tfc"
absolute_path : "/Users/tfc/testdir/foobar.txt"
system_complete : "/Users/tfc/testdir/foobar.txt"
canonical(p) : "/Users/tfc/testdir/foobar.txt"
"testdir/foobar.txt"
canonical testdir : "/Users/tfc/testdir/foobar.txt"
canonical testdir 2 : "/Users/tfc/testdir/foobar.txt"
equivalence: 1
8. Мы не обрабатываем никаких исключений в нашей короткой программе. При удалении файла foobar.txt из каталога testdir программа прекращает свою работу из-за исключения. Функция canonical требует наличия действительного пути файла. Существует также функция weakly_canonical, которая не предъявляет подобных требований.
$ ./canonial_filepath
current_path : "/Users/tfc"
absolute_path : "/Users/tfc/testdir/foobar.txt"
system_complete : "/Users/tfc/testdir/foobar.txt"
terminate called after throwing an instance of
'std::filesystem::v1:: cxx11::filesystem_error'
what(): filesystem error: cannot canonicalize:
No such file or directory [testdir/foobar.txt] [/Users/tfc]
Как это работает
Цель данного примера заключается в том, чтобы увидеть, как легко создавать новые пути динамически. В основном это связано с наличием в классе path удобного перегруженного оператора /. Кроме того, функции файловой системы хорошо работают с абсолютными и относительными путями к файлам, а также с путями, которые содержат косвенные адреса . и ...
Есть довольно много функций, которые возвращают части экземпляра path, иногда даже преобразуя их. Не будем перечислять все существующие функции, поскольку для знакомства с ними лучше всего обратиться к справочным материалам по C++.
Однако функции-члены класса path, возможно, стоит рассмотреть поближе. Посмотрим, каким функциям-членам класса path соответствуют конкретные части пути к файлу. На следующей диаграмме показано, что пути файлов в Windows несколько отличаются от путей файлов в UNIX/Linux (рис. 10.1).
Как видите, функции-члены класса path возвращаются для абсолютного пути. Для относительных путей root_path, root_name и root_directory пусты. relative_ path, соответственно, возвращает путь, только если тот уже является относительным.
Составляем список всех файлов в каталоге
Конечно же, каждая операционная система, предлагающая поддержку файловой системы, также поставляется с утилитой, которая просто перечисляет все файлы внутри каталога в файловой системе. Самые простые примеры — команда ls в Linux, MacOS и других UNIX-подобных операционных системах. В DOS и Windows существует команда dir. Обе команды составляют список из всех файлов в каталоге и предоставляют дополнительную информацию, такую как размер файла, разрешения и т.д.
Однако переопределение такого инструмента также является хорошим примером, который позволит нам научиться выполнять обходы каталогов и файлов. Давайте просто сделаем это!
Наша собственная утилита ls/dir будет способна упорядочивать по имени все файлы в каталоге, их флаги разрешения доступа и отображать количество байт, которые они занимают в файловой системе.
Как это делается
В этом примере мы реализуем небольшой инструмент, который составляет список файлов в любом предоставленном пользователем каталоге. Он будет упорядочивать файлы в списке не только по имени, но и по типу, размеру и разрешениям доступа.
1. Сначала включим необходимые заголовочные файлы и объявим об использовании пространств имен std и filesystem по умолчанию:
#include <iostream>
#include <sstream>
#include <iomanip>
#include <numeric>
#include <algorithm>
#include <vector>
#include <filesystem>
using namespace std;
using namespace filesystem;
2. Понадобится вспомогательная функция file_info. Она принимает ссылку на объект directory_entry и извлекает из нее путь, а также объект file_status (с помощью функции status), который содержит тип файла и информацию о правах. Наконец, она извлекает и размер записи, если это обычный файл. Для каталогов и особых файлов мы просто возвращаем значение 0. Вся информация упаковывается в кортеж.
static tuple<path, file_status, size_t>
file_info(const directory_entry &entry)
{
const auto fs (status(entry));
return {entry.path(),
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'; }