Avatar ·

Как получить список файлов в директории на C++? (кросс-платформенно)

Всем привет! Я начинающий C++ разработчик, и у меня возникла проблема. Мне нужно написать функцию, которая получает список всех файлов в заданной директории. Я работаю на Windows, но хотелось бы, чтобы код работал и на Linux (или macOS) без переписывания. Проблема в том, что я нашел несколько разных способов. Например, на Windows есть WinAPI функции `FindFirstFile` / `FindNextFile`. На Linux — `opendir` / `readdir`. Но это же жутко неудобно: писать `#ifdef _WIN32` и дублировать код. Я слышал, что в C++17 появилось что-то вроде `std::filesystem`. Но я не уверен, работает ли это на практике. Мой компилятор (MSVC 2022) вроде поддерживает C++17, но я боюсь, что на старых серверах или в embedded-системах это может не собраться. Вот пример того, что я пытаюсь сделать (псевдокод): ```cpp #include #include #include // Какую функцию вызвать? Я хочу получить vector с именами файлов. std::vector getFilesInDirectory(const std::string& path) { // ??? } int main() { auto files = getFilesInDirectory("C:\\MyFolder"); for (const auto& f : files) { std::cout << f << std::endl; } return 0; } ``` Мне нужно: 1. Получить только файлы (не папки). 2. Игнорировать "." и ".." (если они есть). 3. Желательно, чтобы можно было фильтровать по расширению (например, только *.txt). Какой способ сейчас считается современным и правильным? Стоит ли использовать `std::filesystem` или лучше остаться на старой школе с `dirent.h`? И если `std::filesystem`, то как правильно обрабатывать ошибки (например, если папки не существует)? Заранее спасибо за помощь! <<>>

Современное решение: std::filesystem (C++17)

Начиная с C++17, стандартная библиотека предоставляет модуль std::filesystem, который решает вашу проблему кросс-платформенно и без танцев с бубном. Это единственный правильный способ для нового кода. Он работает на Windows, Linux, macOS и других POSIX-системах.

Вот полный рабочий пример, который делает именно то, что вы просите:

#include <iostream>
#include <vector>
#include <string>
#include <filesystem>
namespace fs = std::filesystem;

std::vector<std::string> getFilesInDirectory(const std::string& path) {
    std::vector<std::string> files;
    
    try {
        // Проверяем, существует ли путь и является ли он директорией
        if (!fs::exists(path) || !fs::is_directory(path)) {
            std::cerr << "Ошибка: путь не существует или не является директорией: " << path << std::endl;
            return files; // возвращаем пустой вектор
        }

        // Итерация по содержимому директории
        for (const auto& entry : fs::directory_iterator(path)) {
            // Проверяем, что это обычный файл (не директория, не симлинк и т.д.)
            if (entry.is_regular_file()) {
                // Добавляем имя файла (можно также полный путь: entry.path().string())
                files.push_back(entry.path().filename().string());
            }
        }
    } catch (const fs::filesystem_error& e) {
        std::cerr << "Ошибка файловой системы: " << e.what() << std::endl;
    } catch (const std::exception& e) {
        std::cerr << "Общая ошибка: " << e.what() << std::endl;
    }
    
    return files;
}

int main() {
    auto files = getFilesInDirectory("C:\\MyFolder"); // или "/home/user/MyFolder" на Linux
    for (const auto& f : files) {
        std::cout << f << std::endl;
    }
    return 0;
}

Ключевые моменты:

  • Проверка существования и типа: fs::exists(path) и fs::is_directory(path) — обязательны, чтобы избежать исключений.
  • Итерация: fs::directory_iterator автоматически игнорирует "." и ".." — это встроенное поведение.
  • Фильтрация файлов: entry.is_regular_file() отсеивает папки и специальные файлы.
  • Обработка ошибок: Используйте try-catch, так как операции с файловой системой могут кидать исключения (например, нет прав доступа).
  • Кросс-платформенность: Код одинаково работает на Windows (с обратными слешами) и Linux (с прямыми).

Фильтрация по расширению (например, только .txt):

Добавьте проверку расширения внутри цикла:

if (entry.is_regular_file() && entry.path().extension() == ".txt") {
    files.push_back(entry.path().filename().string());
}

Альтернатива: рекурсивный обход поддиректорий:

Если вам нужно собрать файлы из всех подпапок, замените directory_iterator на recursive_directory_iterator:

for (const auto& entry : fs::recursive_directory_iterator(path)) {
    // ... та же логика
}

Важно: Убедитесь, что ваш компилятор поддерживает C++17. В Visual Studio 2022 это включено по умолчанию. Для GCC/MinGW добавьте флаг -std=c++17 и линкуйтесь с -lstdc++fs (на старых версиях) или просто -std=c++17 (на новых).

Этот подход — самый современный, безопасный и рекомендуемый. Забудьте про WinAPI и dirent.h, если только вы не пишете код для очень старых компиляторов.

<<>>

Классический подход: dirent.h (для C++11/14 или embedded)

Если по каким-то причинам вы не можете использовать C++17 (например, проект на древнем компиляторе или микроконтроллере), то можно использовать POSIX-совместимую библиотеку dirent.h. Она работает на Linux и macOS "из коробки", а на Windows её можно эмулировать (есть порт dirent для Windows).

#include <iostream>
#include <vector>
#include <string>
#include <dirent.h> // POSIX

std::vector<std::string> getFilesInDirectory(const std::string& path) {
    std::vector<std::string> files;
    DIR* dir = opendir(path.c_str());
    
    if (!dir) {
        std::cerr << "Не удалось открыть директорию: " << path << std::endl;
        return files;
    }
    
    struct dirent* entry;
    while ((entry = readdir(dir)) != nullptr) {
        std::string name = entry->d_name;
        // Игнорируем "." и ".."
        if (name == "." || name == "..") continue;
        
        // На POSIX системах можно проверить тип файла через entry->d_type
        // Но это не обязательно: просто собираем всё, кроме "." и ".."
        // Для фильтрации только файлов нужно делать stat()
        files.push_back(name);
    }
    closedir(dir);
    return files;
}

Недостатки:

  • Не кросс-платформенно "из коробки" (на Windows нужна отдельная библиотека).
  • Нет встроенной фильтрации "только файлы" — нужно делать дополнительный вызов stat() для каждого элемента.
  • Ручное управление памятью (закрытие дескриптора).
  • Небезопасно для многопоточности (если не использовать реентерабельные версии).

Этот метод — наследие прошлого. Используйте его только если std::filesystem недоступен. Для нового кода всегда выбирайте C++17.