§34 Файловая система

Путь к директории или файлу. Класс path

В этом уроке мы познакомимся с библиотекой Filesystem, которая была включена в std стандартом C++17. Это довольно внушительная библиотека, содержащая большое множество классов и функций для работы с файловой системой. Перед тем, как начать знакомство с библиотекой, давайте вспомним как в системе можно определить путь к файлу или директории (папке).
Пользователь системы всегда находится в определенной директории (по умолчанию в домашней). Указать путь к файлу или директории, относительно текущей, можно двумя способами. Первый способ – это использовать относительный путь (relative path), а второй – использовать полный путь (absolute path). Рассмотрим оба варианта.

Относительный путь

Относительный путь удобно применять в том случае, если нужно избегать привязки к коревой директории (например, если она потенциально может измениться).
Условные обозначения:
. (одна точка) – текущий файл или директория
.. (две точки) – перейти на уровень вверх (в родительскую директорию)
/ – разделитель директорий (directory-separator).

Здесь освещается работа только c файловой системой OS Linux. Корневая директория в Linux имеет обозначение – / (слэш). В Windows разделитель директорий \ (бэк-слэш), но это в C++ специальный символ, поэтому требуется экранирование дополнительным бэк-слэшем (\\): "C:\\dir1\\dir2".

Приведем пример (см. рисунок выше). Допустим, в качестве текущей директории выбрана “Музыка”, а вам нужно установить путь к файлу mc.menu, находящемуся в директории /etc/mc/. Относительный путь будет выглядеть следующим образом:

../../../etc/mc/mc.menu

Троекратное использование ../ означает то, что нам нужно подняться на три уровня выше по иерархии (до корневой директории), затем посетить директорию etc и, находящуюся в ней, директорию mc. Если нужно указать файл или папку в текущей директории, то используется ./:

./1.mp3
Полный путь

Полный путь всегда определяется от корневой директории (вне зависимости от текущей выбранной папки). Полный путь к файлам mc.menu и 1.mp3 будет выглядеть следующим образом:

/etc/mc/mc.menu
/home/andrew/Музыка/1.mp3
class path

Для того, чтобы представить путь в файловой системе используются объекты класса path. Пути неявно преобразуются в std::basic_string, что позволяет использовать их в качестве аргументов, например, для std::ifstream::open.

Программа cpp-34.1
#include <fstream>
#include <iostream>
#include <filesystem>

using namespace std;
using namespace filesystem;

int main() {
    path f = "/home/andrew/QT_projects/file1/";
    if (f.is_absolute())
        cout << "Используется полный путь\n"
             << f.string() // Конвертирование пути в строку
             << endl;
    else
        cout << "Используется относительный путь"
             << f.string()
             << endl;
    f /= "file1.txt";     // Добавляем имя файла к пути
    ofstream(f.string()); // Создать в директории проекта файл file1.txt
    cout << "Новый путь:\n"
         << f.string()
         << endl;
    return 0;
}

Вывод

Используется полный путь
/home/andrew/QT_projects/file1/
Новый путь:
/home/andrew/QT_projects/file1/file1.txt

Класс path обладает большим количеством методов. Ниже приводятся некоторые из них:

  • assign – заменить на новый
  • append, operator/= – добавить в конец
  • concat, operator+= – конкатенация
  • clear – очистка
  • remove_filename – удаляет имя файла (или каталога) в конце пути
  • replace_filename – поменять имя файла на новое
  • begin, end – итератор доступа к последовательности элементов пути
  • u8path – создает путь из UTF-8 std::string, std::string_view или char8_t

Директория. Класс directory_iterator

Рассмотрим подробнее функции библиотеки для работы с директориями.

create_directory, create_directories
create_directory(path& p [, path& ex_p] [, std::error_code& ec])
create_directories(path& p [, std::error_code& ec])

Создает директорию или директории в пути. Возвращают булевский тип успешности операции (true, если создание директории в пути разрешено).

  • p – путь
  • ex_p – путь из которого копируются атрибуты
  • ec – выходной параметр для сообщений об ошибке
Отличаются тем, что в первом случае все директории в пути должны существовать.

copy
copy(path& from, path& to [, copy_options options] [, std::error_code& ec])

Копирует файлы и директории. copy_options устанавливает параметры копирования.

Параметры копирования. copy_options
copy_options
Константа Описание
skip_existing Сохранить, но не сообщать об ошибках
overwrite_existing Заменить существующий файл
update_existing Замените существующий файл, только если он старше, чем копируемый файл
recursive Рекурсивно копировать поддиректории и их содержимое
copy_symlinks Копировать только символические ссылки, а не файлы, на которые они указывают
skip_symlinks Игнорировать символические ссылки
directories_only Копировать только структуру директорий, а не файлы относящиеся к директориям
create_symlinks Вместо создания копий файлов создавать символические ссылки, указывающие на оригиналы. Примечание: исходный путь должен быть абсолютным, если путь назначения не находится в текущей директории
create_hard_links Вместо создания копий файлов создаются жесткие ссылки

Несколько опций можно объединить битовой маской с помощью побитовых операций operator&, operator|, operator^, operator~, operator&=, operator|=, и operator^=:

copy(p_from, p_to, copy_options::recursive | copy_options::skip_symlinks);
remove, remove_all
remove(path& p [, std::error_code& ec])
remove_all(path& p [, std::error_code& ec])

1. Удаляет файл, директорию и символическую ссылку (но не цель). Возвращает булевский тип (true, если файл был удален, false, если он не существует).
2. Рекурсивно удаляет содержимое p (если это директория) и содержимое всех его поддиректорий, а затем удаляет и сам p. Возвращает количество файлов и директорий, которые были удалены (или 0, если p не существует).

rename
rename(path& old_p, path& new_p [, std::error_code& ec])

Перемещает или переименовывает объект файловой системы, идентифицируемый old_p, в new_p.

directory_iterator

Класс directory_iterator предоставляет LegacyInputIterator, который перебирает элементы директорий (без их посещений). Порядок итераций не указан, за исключением того, что каждая запись директории прочитывается только один раз. Специальные пути пропускаются. directory_iterator поддерживает диапазонный цикл for. По умолчанию символические ссылки не используются, но это можно изменить, если во время создания итератора указать параметр std::filesystem::directory_optionsfollow_directory_symlink (см. ниже).

    Операции:

  • operator=
  • operator*
  • operator->
  • incrementoperator++
directory_options
Константа Описание
follow_directory_symlink Не пропускать символические ссылки
skip_permission_denied Пропускать директории в которых установленные привилегии могут привести к ошибкам

В программе ниже покажем содержимое директории проекта.

Программа cpp-34.2 (фрагмент, продолжение 34.1)
f.remove_filename(); // Вернемся в корень проекта
for(auto &p: directory_iterator(f))
    std::cout << p.path() << '\n';

Вывод

"/home/andrew/QT_projects/file1/main.cpp"
"/home/andrew/QT_projects/file1/file1.pro"
"/home/andrew/QT_projects/file1/file1.txt"
"/home/andrew/QT_projects/file1/file1.pro.user"

Обратите внимание, что в процессе работы итератора текущая директория будет изменяться. Узнать какая директория в данный момент выбрана можно с помощью функции current_path (см. ниже).

recursive_directory_iterator

Класс recursive_directory_iterator предоставляет LegacyInputIterator, который выполняет итерацию по элементам директории и, рекурсивно, всех его поддиректорий. Порядок итераций не указан, за исключением того, что каждая запись директории прочитывается только один раз. По умолчанию символические ссылки не используются, но это можно изменить, если во время создания итератора указать параметр std::filesystem::directory_optionsfollow_directory_symlink.
Рекурсивный перебор директорий может быть остановлен с помощью метода disable_recursion_pending.

Программа cpp-34.3
#include <fstream>
#include <iostream>
#include <filesystem>

using namespace std;
using namespace filesystem;

int main() { // Создаем директории в пути (в папке проекта)
    create_directories("/home/andrew/QT_projects/file2/a/b/c/d/e/f/");
    path f = "/home/andrew/QT_projects/file2/file1.txt"; // Создаем путь и
    ofstream(f.string()); // файл по этому пути
    f.remove_filename();  // убираем имя файла в пути
    auto first = recursive_directory_iterator(f); // begin
    auto last  = recursive_directory_iterator();  // end
    while (first != last) { // Ищем директорию с именем "f"
        if (first -> path().filename() == "f") { // если нашли, то
            first.disable_recursion_pending(); // останавливаемся и
            // делаем что-то, например, переносим файл на новое место
            rename("/home/andrew/QT_projects/file2/file1.txt",
                   first -> path() / "file.1.txt");
            break; // возобновить нельзя
         } else { // Пока не нашли, выводим пути всех объектов в директории
            cout << first -> path() << endl;
            first++;
        }
    }
    return 0;
}

Вывод

"/home/andrew/QT_projects/file2/main.cpp"
"/home/andrew/QT_projects/file2/file2.pro"
"/home/andrew/QT_projects/file2/a"
"/home/andrew/QT_projects/file2/a/b"
"/home/andrew/QT_projects/file2/a/b/c"
"/home/andrew/QT_projects/file2/a/b/c/d"
"/home/andrew/QT_projects/file2/a/b/c/d/e"
current_path
current_path([std::error_code& ec])
current_path(path& p [, std::error_code& ec])

1. Возвращает абсолютный путь текущего рабочего каталога.
2. Изменяет текущий рабочий каталог на p.

is_directory
is_directory( std::filesystem::file_status s)
is_directory(path& p [, std::error_code& ec])

Проверяет, соответствует ли данный статус файла или путь директории.
1. Эквивалент s.type() == file_type::directory.
2. Эквивалентно is_directory(status(p)) или is_directory(status(p, ec)), соответственно.

temp_directory_path

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

path temp_directory_path([std::error_code& ec])

Файлы и ссылки

Проверка типа файла
  • is_block_file – Проверяет, соответствует ли данный статус файла или путь специальному блочному файлу. (Пример блочного файла: /dev/sda, /dev/loop0);
  • is_character_file – Проверяет, соответствует ли данный статус файла или путь специальному символьному файлу. (Пример символьного файла: /dev/null, /dev/tty);
  • is_empty – Проверяет, относится ли данный путь к пустому файлу или каталогу;
  • is_fifo – Проверяет, соответствует ли данный статус файла или путь файлу FIFO;
  • is_other – Проверяет, соответствует ли данный статус файла или путь файлу, который не является ни обычным файлом, ни каталогом, ни символической ссылкой;
  • is_regular_file – Проверяет, соответствует ли данный статус файла или путь обычному файлу;
  • is_socket – Проверяет, соответствует ли данный статус файла или путь указанному сокету IPC;
  • is_symlink – Проверяет, соответствует ли данный статус файла или путь символической ссылке;
  • status_known – Проверяет, известен ли данный статус файла. Эквивалентно s.type() != file_type::none.
Программа cpp-34.4
#include <iostream>
#include <filesystem>
#include <array>
#include <chrono>

using namespace std;
using namespace filesystem;
using namespace chrono;

int main() {
    array<int, 3> dir {};
    path f = "/home/andrew/";
    auto start = system_clock::now();
    for(auto &p: recursive_directory_iterator(f)) {
        if (is_directory(p))    dir[0]++;
        if (is_regular_file(p)) dir[1]++;
        if (is_symlink(p))      dir[2]++;
    }
    auto end = system_clock::now();
    auto elapsed = end - start;
    cout << "Директорий: " << dir[0]
         << "\nОбычных файлов: " << dir[1]
         << "\nСимволических ссылок: " << dir[2]
         << "\nВремя, затраченное на поиск файлов: "
         << duration_cast<seconds>(elapsed).count()
         << " с"
         << endl;
    return 0;
}

Вывод

Директорий: 11903
Обычных файлов: 196208
Символических ссылок: 44268
Время, затраченное на поиск файлов: 8 с
copy_file
copy_file(path& from, path& to [, copy_options options] [, std::error_code& ec])

Копирует один файл из from в to, используя параметры копирования. Поведение не определено, если в copy_options присутствует несколько опций.

file_size
file_size(path& p [, std::error_code& ec])

Возвращает размер файла. Будет следовать символической ссылке. Попытка определения размера директории определяется реализацией.

resize_file
resize_file(path& p, std::uintmax_t new_size [, std::error_code& ec])

Изменяет размер обычного файла. Если размер файла был больше, чем new_size, оставшаяся часть файла отбрасывается. Если файл ранее был меньше, чем new_size, размер файла увеличивается, и новая область заполняется нулями.

Символические ссылки. create_symlink

Символичеcкие ссылки (symbolic links) – это специальные файлы, которые содержат путь к файлам или директориям. Ссылки, как и файлы, обладают собственным именем и правами доступа, но жестко к файлам не привязаны, а права на файлы могут отличаться от прав символических ссылок, которые на них указывают. На один и тот же файл могут ссылаться несколько символических ссылок. Если удалить символическую ссылку, то файл, на который она ссылается, не будет удален. Если файл удален (а так же перемещен или переименован), то ссылка будет ссылаться на несуществующий ресурс.
Файловая библиотека имеет несколько функций для работы с символическими ссылками. Создать символическую ссылку можно с помощью функций create_symlink и create_directory_symlink

create_symlink(path& target, path& link [, std::error_code& ec])
create_directory_symlink(path& target, path& link [, std::error_code& ec])

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

copy_symlink
copy_symlink(path& from, path& to [, std::error_code& ec])

Копирует символическую ссылку в другое место.

read_symlink
read_symlink(path& p [, std::error_code& ec]);

Возвращает путь цели символической ссылки или ошибку, если p не является символической ссылкой или путь не символичеcкая ссылка. При ошибке возвращается пустой путь.

Жесткая ссылка. create_hard_link

Жесткая ссылка (hard link) – это псевдоним файла. У файла может быть несколько жёстких ссылок под различными именами. При редактировании файла через одну из ссылок на него, содержимое по другим ссылкам тоже изменится. Количество жёстких ссылок файла сохраняется на уровне файловой системы в метаинформации. В файловых системах UNIX-подобных ОС и в NTFS при создании файла на него автоматически создаётся одна жёсткая ссылка (на то место файловой системы, в котором создаётся файл). Жесткая ссылка имеет те же права, что и целевой файл. В связи с тем, что жёсткие ссылки ссылаются на индексный дескриптор, уникальный в пределах дискового раздела, создание жёсткой ссылки на файл в директории другого раздела невозможно. Жесткие ссылки нельзя создавать для директорий.
Для создания жесткой ссылки используется функция create_hard_link.

create_hard_link(path& target, path& link [, std::error_code& ec])
hard_link_count
hard_link_count(path& p [, std::error_code& ec])

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

Права на файлы и директории

Символические ссылки, файлы и директории имеют в UNIX-подобных ОС три вида прав доступа к ресурсу:

  • Право на чтение (r)
  • Право на запись (w)
  • Право на исполнение (x)
Этими правами наделяются три группы пользователей:
  • Владелец
  • Группа владельца
  • Все остальные
Права на ресурс определяются одновременно для всех категорий в очередности, указанной выше.
Помимо прав в формате rwxrwxrwx существуют дополнительные биты SGID, SUID и sticky bit. Использование битов SUID или SGID позволяют запускать файл на выполнение с правами владельца файла или группы соответственно. sticky bit (t-бит) используется только с директориями. Когда t-бит для директории не установлен, файл в данной директории может удалить (переименовать) любой пользователь, имеющий доступ на запись к данной директории. Устанавливая t-бит на директорию, можно изменить это правило так, что удалить (переименовать) файл мог только владелец этого файла.
Класс перечисление perms содержит константы, определяющие права на файлы и директории.

Константы std::filesystem::perms
Константа Восьмеричное значение Описание
none 000 биты разрешения не установлены
owner_read 400 Владелец файла имеет разрешение на чтение
owner_write 200 Владелец файла имеет разрешение на запись
owner_exec 100 Владелец файла имеет разрешение на выполнение/поиск
owner_all 700 Владелец файла имеет права на чтение, запись и выполнение/поиск. Эквивалентно: owner_read | owner_write | owner_exec
group_read 040 Группа пользователя файла имеет разрешение на чтение
group_write 020 Группа пользователя файла имеет разрешение на запись
group_exec 010 Группа пользователя файла имеет разрешение на выполнение/поиск
group_all 070 Группа пользователя файла имеет разрешения на чтение, запись и выполнение/поиск
others_read 004 Все остальные пользователи имеют разрешение на чтение
others_write 002 Все остальные пользователи имеют разрешение на запись
others_exec 001 Все остальные пользователи имеют разрешение на выполнение/поиск
others_all 007 Все остальные пользователи имеют права на чтение, запись и выполнение/поиск. Эквивалентно others_read | others_write | others_exec
all 777 Все пользователи имеют права на чтение, запись и выполнение/поиск. Эквивалентно owner_all | group_all | others_all
set_uid 4000 Установить идентификатор пользователя для владельца файла на выполнение
set_gid 2000 Установить идентификатор группы для идентификатора группы пользователей файла на выполнение
sticky_bit 1000 Значение, определяемое реализацией. При установке – только владельцы файлов могут удалять файлы в директории, даже если директория доступна для записи другим пользователям (используется с /tmp)

Тип perms удовлетворяет требованиям BitmaskType, т. е. определены побитовые операции operator&, operator|, operator^, operator~, operator&=, operator|= и operator^=.
Класс perm_options предоставляет параметры, которые управляют поведением функции permissions (см. ниже). perm_options удовлетворяет требованиям BitmaskType (т. е. определены побитовые операции перечисленные выше).

perm_options
Константа Описание
replace Права будут полностью заменены аргументом для permissions() (поведение по умолчанию)
add Права будут заменены на аргументы perms с побитовым ИЛИ и текущим разрешением
remove Права будут заменены на аргументы perms с побитовым И для отрицания и текущим разрешением
nofollow Права будут изменены для самой символической ссылки, а не для файла, на который она ссылается
permissions
permissions(path& p, perms prms [, perm_options opts = perm_options::replace]);
permissions(path& p, perms prms [, perm_options opts], std::error_code& ec);

Изменяет права доступа к файлу, к которому разрешает путь p. Переход по символическим ссылкам, если perm_options::nofollow не установлен в opts.

Программа cpp-34.5
#include <fstream>
#include <iostream>
#include <filesystem>

using namespace std;
using namespace filesystem;

void demo_perms(perms p) { // Функция для вывода прав на файл
    cout << ((p & perms::owner_read)   != perms::none ? "r" : "-")
         << ((p & perms::owner_write)  != perms::none ? "w" : "-")
         << ((p & perms::owner_exec)   != perms::none ? "x" : "-")
         << ((p & perms::group_read)   != perms::none ? "r" : "-")
         << ((p & perms::group_write)  != perms::none ? "w" : "-")
         << ((p & perms::group_exec)   != perms::none ? "x" : "-")
         << ((p & perms::others_read)  != perms::none ? "r" : "-")
         << ((p & perms::others_write) != perms::none ? "w" : "-")
         << ((p & perms::others_exec)  != perms::none ? "x" : "-")
         << endl;
}
int main() {
    ofstream("test.txt"); //  Создаем файл
    cout << "Созданный файл имеет права: ";
    demo_perms(status("test.txt").permissions());
    permissions("test.txt", // Изменяем права
                perms::owner_all |
                perms::group_all,
                perm_options::add);
    cout << "Добавлены права o+rwx и g+rwx:  ";
    demo_perms(status("test.txt").permissions());
    remove("test.txt"); // удаляем файл
    return 0;
}

Для того, чтобы показать права на все файлы в домашней директории воспользуемся итераторами по директории. Чтобы пути выводились без кавычек, будем конвертировать путь в строку, а затем небольшими манипуляциями произведем выравнивание:

Программа cpp-34.6 (фрагмент)
path f = "/home/andrew/";
for(auto &p: directory_iterator(f)) {
    string S = p.path().string();
    if (S.size() > 20) { // Сокращаем длинный путь
        S.resize(20);    // для красивого вывода
        S += "...";
    } else {
        S.resize(S.size() + (23 - S.size()), ' ');
    }
    cout << S << "\t";
    demo_perms(status(p.path()).permissions());
}

Фрагмент вывода

/home/andrew/впр_...    rw-rw-r--
/home/andrew/.thumbn... rwx------
/home/andrew/.design... rwxrwxr-x
/home/andrew/.svnqt     rwxrwxr-x
/home/andrew/.skycha... rwxrwxr-x
/home/andrew/.eclips... rwxrwxr-x
/home/andrew/.pki       rwxrw----
/home/andrew/.fonts.... rw-rw-r--
/home/andrew/py_proj... rwxrwxr-x
/home/andrew/MyProje... rwxrwxr-x
/home/andrew/shCore.... rw-r--r--
/home/andrew/eclipse... rwxrwxr-x
/home/andrew/.kde       rwxrwxr-x
/home/andrew/.dvisvg... rwxrwxr-x
/home/andrew/.qt        rwxrwxr-x
/home/andrew/.recent... rw-------
/home/andrew/.mcoprc    rw-------
/home/andrew/.gtkrc-... rw-rw-r--
Примеры решения задач
Вопросы
Темы сообщений
Задания А
Задания Б
Задания С
Ссылки
1 Звезда2 Звезды3 Звезды4 Звезды5 Звезд (1 оценок, среднее: 5,00 из 5)
Загрузка...

Если вы нашли ошибку, пожалуйста, выделите фрагмент текста и нажмите Ctrl+Enter.

Print Friendly, PDF & Email

Обсуждение закрыто.