if (!exists(dir)) {
cout << "Path " << dir << " does not exist.\n";
return 1;
}
4. Итак, на данном этапе мы вполне уверены: пользователь предоставил существующий путь к файлу, зная, что мы можем запросить нормализованную версию, которую затем отобразим. Функция
filesystem::canonical
возвращает другой объект класса path
. Мы можем вывести на экран его напрямую, но перегруженный оператор <<
класса path
берет в кавычки пути к файлу. Чтобы этого избежать, можем отобразить путь к файлу с помощью методов .c_str()
или .string()
:
cout << canonical(dir).c_str() << '\n';
}
5. Скомпилируем программу и поработаем с ней. Когда мы запустим ее в домашнем каталоге и передадим относительный путь к файлу
"src"
, она выведет на экран полный абсолютный путь к файлу.
$ ./normalizer src
/Users/tfc/src
6. Когда мы снова запускаем программу в домашнем каталоге, но даем ей запутанное относительное описание пути к файлу, в котором сначала прописывается вход в папку Desktop, потом прописывается выход из нее с помощью косвенного адреса
..
, затем входим в папку Documents и выходим из нее, чтобы в конечном итоге попасть в каталог src
, программа отображает тот же путь файла, что и ранее!
$ ./normalizer Desktop/../Documents/../src
/Users/tfc/src
Как это работает
Этот начальный пример работы с
std::filesystem
мы сделали относительно коротким и прямолинейным. Мы инициализировали объект класса path на основе строки, которая содержит описание пути к файлу. Класс std::filesystem::path
имеет самое первостепенное значение в тех ситуациях, когда мы используем библиотеку, связанную с файловой системой, поскольку с ним связано большинство функций и классов.Функция
filesystem::exists
позволяет проверить, существует ли указанный путь файла на самом деле. До сего момента мы не можем быть в этом уверены, поскольку возможно создавать объекты класса path
, которые не относятся к реальному объекту файловой системы. Функция exists
всего лишь принимает экземпляр класса path
и возвращает значение true в том случае, если он действительно существует. Эта функция способна сама определить, какой путь мы ей передали (абсолютный или относительный), что делает ее очень комфортной в применении.Наконец, мы использовали функцию
filesystem::canonical
для каталога, чтобы вывести на экран его нормализованную форму:
path canonical(const path& p, const path& base = current_path());
Функция
canonical
принимает путь к файлу и в качестве необязательного второго аргумента еще один путь к файлу. Второй путь base
добавляется к пути файла p
в том случае, если p
является относительным. После этого функция canonical
пытается убрать все косвенные адреса .
и ..
.Для вывода результата на экран мы использовали метод
.c_str()
, которому передали канонический путь к файлу. Мы сделали так потому, что перегруженный оператор <<
для выходных потоков берет пути к файлам в кавычки, а это не всегда желательно.
Дополнительная информация
Функция
canonical
генерирует исключение типа fileystem_error
, если путь, который мы хотим привести к каноническому виду, не существует. Для предотвращения этого мы проверили наш путь к файлу с помощью функции exists
. Но было ли достаточно данной проверки, чтобы необработанные исключения не генерировались? Нет.Обе функции, как
exists
, так и canonical
, способны генерировать исключения типа bad_alloc
. Если бы эти исключения сгенерировались, кто-то мог бы утверждать, что программа все равно обречена. Более важная, а также гораздо более вероятная проблема возникает, когда где-то между проверкой существования файла и процессом приведения его к каноническому виду некто переименовывает или удаляет основной файл! В этом случае функция canonical
сгенерирует сообщение об ошибке filesystem_error
, хотя мы ранее уже убедились в том, что файл существует.Большая часть функций файловой системы имеет еще одну перегруженную версию, которая принимает те же аргументы, а также ссылку на
std::error_code
:
path canonical(const path& p, const path& base = current_path());
path canonical(const path& p, error_code& ec);
path canonical(const std::filesystem::path& p,
const std::filesystem::path& base,
std::error_code& ec);
Таким образом, можно выбрать, окружать ли запросы к функциям файловой системы конструктами
try-catch
, или же проверять наличие ошибок вручную. Обратите внимание: это изменяет только поведение ошибок, связанных с файловой системой! Если в системе заканчивается память, то возможна генерация более сложных исключений, таких как bad_alloc
, которые могут иметь или не иметь параметр ec
. Получаем канонические пути к файлам из относительных путей
В последнем примере мы уже приводили к каноническому виду/нормализовали пути файлов. Конечно же, класс
filesystem::path
способен не только хранить и проверять пути к файлам. Это помогает легко создавать пути к файлу из строк, а также снова разбивать их на составные части.На данном этапе класс path позволяет абстрагироваться от деталей работы операционной системы, но в некоторых случаях все же о них следует помнить.
Мы увидим, как обращаться с путями и их композицией/декомпозицией, на примере работы с абсолютными и относительными путями.
Как это делается
В данном примере мы будем работать с абсолютными и относительными путями к файлам, чтобы увидеть сильные стороны класса
path
и связанных с ним вспомогательных функций.
1. Сначала включаем все необходимые заголовочные файлы и объявляем, что используем пространства имен
std
и filesystem
:
#include
#include
using namespace std;
using namespace filesystem;
2. Затем объявляем пример пути к файлу. На данный момент неважно, существует ли текстовый файл, на который он ссылается. Тем не менее есть функции, генерирующие исключения, если требуемого файла нет.
int main()
{
path p {"testdir/foobar.txt"};
3. Сейчас мы познакомимся с четырьмя разными функциями библиотеки для работы с файловой системой. Функция
current_path
возвращает путь, в котором в данный момент выполняется программа, — так называемый рабочий каталог. Функция absolute
принимает относительный путь к файлу наподобие нашего пути p
и возвращает абсолютный, однозначный путь во всей файловой системе.Функция
system_complete
делает практически то же самое, что и функция absolute
в Linux, MacOS или других UNIX-подобных операционных системах. В Windows мы получим абсолютный путь к файлу, только вначале будет добавлено буквенное обозначение тома диска (например, "C:"
). Функция canonical
опять же делает то же самое, что и функция absolute
, но потом дополнительно убирает все косвенные адреса, такие как ".
" (сокращение для «текущий каталог») или "..
" (сокращение для «один каталог вверх»). Мы рассмотрим работу с данными косвенными адресами в следующих шагах.
cout << "current_path : " << current_path()
<< "\nabsolute_path : " << absolute(p)
<