¿Cómo se itera a través de cada archivo/directorio de forma recursiva en C++ estándar?

StackOverflow https://stackoverflow.com/questions/67273

  •  09-06-2019
  •  | 
  •  

Pregunta

¿Cómo se itera a través de cada archivo/directorio de forma recursiva en C++ estándar?

¿Fue útil?

Solución

En C++ estándar, técnicamente no hay forma de hacer esto ya que C++ estándar no tiene concepción de directorios.Si desea ampliar su red un poco, es posible que desee considerar el uso Impulsar.FileSystem.Esto ha sido aceptado para su inclusión en TR2, por lo que le brinda la mejor oportunidad de mantener su implementación lo más cerca posible del estándar.

Un ejemplo, tomado directamente del sitio web:

bool find_file( const path & dir_path,         // in this directory,
                const std::string & file_name, // search for this name,
                path & path_found )            // placing path here if found
{
  if ( !exists( dir_path ) ) return false;
  directory_iterator end_itr; // default construction yields past-the-end
  for ( directory_iterator itr( dir_path );
        itr != end_itr;
        ++itr )
  {
    if ( is_directory(itr->status()) )
    {
      if ( find_file( itr->path(), file_name, path_found ) ) return true;
    }
    else if ( itr->leaf() == file_name ) // see below
    {
      path_found = itr->path();
      return true;
    }
  }
  return false;
}

Otros consejos

Si usa la API Win32, puede usar el Buscar primer archivo y Buscar siguiente archivo funciones.

http://msdn.microsoft.com/en-us/library/aa365200(VS.85).aspx

Para el recorrido recursivo de directorios, debe inspeccionar cada uno de ellos. WIN32_FIND_DATA.dwFileAttributes para comprobar si el FILE_ATTRIBUTE_DIRECTORY El bit está configurado.Si el bit está establecido, puede llamar recursivamente a la función con ese directorio.Alternativamente, puede usar una pila para proporcionar el mismo efecto de una llamada recursiva pero evitando el desbordamiento de la pila para árboles de ruta muy largos.

#include <windows.h>
#include <string>
#include <vector>
#include <stack>
#include <iostream>

using namespace std;

bool ListFiles(wstring path, wstring mask, vector<wstring>& files) {
    HANDLE hFind = INVALID_HANDLE_VALUE;
    WIN32_FIND_DATA ffd;
    wstring spec;
    stack<wstring> directories;

    directories.push(path);
    files.clear();

    while (!directories.empty()) {
        path = directories.top();
        spec = path + L"\\" + mask;
        directories.pop();

        hFind = FindFirstFile(spec.c_str(), &ffd);
        if (hFind == INVALID_HANDLE_VALUE)  {
            return false;
        } 

        do {
            if (wcscmp(ffd.cFileName, L".") != 0 && 
                wcscmp(ffd.cFileName, L"..") != 0) {
                if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
                    directories.push(path + L"\\" + ffd.cFileName);
                }
                else {
                    files.push_back(path + L"\\" + ffd.cFileName);
                }
            }
        } while (FindNextFile(hFind, &ffd) != 0);

        if (GetLastError() != ERROR_NO_MORE_FILES) {
            FindClose(hFind);
            return false;
        }

        FindClose(hFind);
        hFind = INVALID_HANDLE_VALUE;
    }

    return true;
}

int main(int argc, char* argv[])
{
    vector<wstring> files;

    if (ListFiles(L"F:\\cvsrepos", L"*", files)) {
        for (vector<wstring>::iterator it = files.begin(); 
             it != files.end(); 
             ++it) {
            wcout << it->c_str() << endl;
        }
    }
    return 0;
}

Puedes hacerlo aún más simple con el nuevo C++11 basado en rango for y Aumentar:

#include <boost/filesystem.hpp>

using namespace boost::filesystem;    
struct recursive_directory_range
{
    typedef recursive_directory_iterator iterator;
    recursive_directory_range(path p) : p_(p) {}

    iterator begin() { return recursive_directory_iterator(p_); }
    iterator end() { return recursive_directory_iterator(); }

    path p_;
};

for (auto it : recursive_directory_range(dir_path))
{
    std::cout << it << std::endl;
}

En C++ 11/14 con el "Filesystem TS", el <experimental/filesystem> encabezado y rangofor simplemente puedes hacer esto:

#include <experimental/filesystem>

using std::experimental::filesystem::recursive_directory_iterator;
...
for (auto& dirEntry : recursive_directory_iterator(myPath))
     cout << dirEntry << endl;

A partir de C++17, std::filesystem es parte de la biblioteca estándar y se puede encontrar en la <filesystem> encabezado (ya no es "experimental").

Una solución rápida es usar C Dirent.h biblioteca.

Fragmento de código de trabajo de Wikipedia:

#include <stdio.h>
#include <dirent.h>

int listdir(const char *path) {
    struct dirent *entry;
    DIR *dp;

    dp = opendir(path);
    if (dp == NULL) {
        perror("opendir: Path does not exist or could not be read.");
        return -1;
    }

    while ((entry = readdir(dp)))
        puts(entry->d_name);

    closedir(dp);
    return 0;
}

Además del boost::filesystem mencionado anteriormente, es posible que desees examinar wxWidgets::wxDir y Qt::QDir.

Tanto wxWidgets como Qt son marcos C++ multiplataforma de código abierto.

wxDir proporciona una forma flexible de recorrer archivos de forma recursiva utilizando Traverse() o uno más simple GetAllFiles() función.También puedes implementar el recorrido con GetFirst() y GetNext() funciones (supongo que Traverse() y GetAllFiles() son contenedores que eventualmente usan las funciones GetFirst() y GetNext()).

QDir proporciona acceso a estructuras de directorios y sus contenidos.Hay varias formas de recorrer directorios con QDir.Puede iterar sobre el contenido del directorio (incluidos los subdirectorios) con QDirIterator del que se creó una instancia con el indicador QDirIterator::Subdirectories.Otra forma es utilizar la función GetEntryList() de QDir e implementar un recorrido recursivo.

Aquí hay un código de muestra (tomado de aquí # Ejemplo 8-5) que muestra cómo iterar sobre todos los subdirectorios.

#include <qapplication.h>
#include <qdir.h>
#include <iostream>

int main( int argc, char **argv )
{
    QApplication a( argc, argv );
    QDir currentDir = QDir::current();

    currentDir.setFilter( QDir::Dirs );
    QStringList entries = currentDir.entryList();
    for( QStringList::ConstIterator entry=entries.begin(); entry!=entries.end(); ++entry) 
    {
         std::cout << *entry << std::endl;
    }
    return 0;
}

Boost::filesystem proporciona recursive_directory_iterator, que es bastante conveniente para esta tarea:

#include "boost/filesystem.hpp"
#include <iostream>

using namespace boost::filesystem;

recursive_directory_iterator end;
for (recursive_directory_iterator it("./"); it != end; ++it) {
    std::cout << *it << std::endl;                                    
}

Puedes usar ftw(3) o nftw(3) recorrer una jerarquía de sistema de archivos en C o C++ en POSIX sistemas.

No lo haces.El estándar C++ no tiene concepto de directorios.Depende de la implementación convertir una cadena en un identificador de archivo.El contenido de esa cadena y a qué se asigna depende del sistema operativo.Tenga en cuenta que C++ se puede usar para escribir ese sistema operativo, por lo que se usa en un nivel en el que aún no está definido preguntar cómo iterar a través de un directorio (porque está escribiendo el código de administración del directorio).

Consulte la documentación de la API de su sistema operativo para saber cómo hacer esto.Si necesita ser portátil, tendrá que tener un montón de #ifdefs para varios sistemas operativos.

Probablemente sería mejor con boost o con el sistema de archivos experimental de c++14. SI está analizando un directorio interno (es decir.utilizado para que su programa almacene datos después de cerrar el programa), luego cree un archivo de índice que tenga un índice del contenido del archivo.Por cierto, probablemente necesites usar boost en el futuro, así que si no lo tienes instalado, ¡instálalo!En segundo lugar, podrías utilizar una compilación condicional, por ejemplo:

#ifdef WINDOWS //define WINDOWS in your code to compile for windows
#endif

El código para cada caso se toma de https://stackoverflow.com/a/67336/7077165

#ifdef POSIX //unix, linux, etc.
#include <stdio.h>
#include <dirent.h>

int listdir(const char *path) {
    struct dirent *entry;
    DIR *dp;

    dp = opendir(path);
    if (dp == NULL) {
        perror("opendir: Path does not exist or could not be read.");
        return -1;
    }

    while ((entry = readdir(dp)))
        puts(entry->d_name);

    closedir(dp);
    return 0;
}
#endif
#ifdef WINDOWS
#include <windows.h>
#include <string>
#include <vector>
#include <stack>
#include <iostream>

using namespace std;

bool ListFiles(wstring path, wstring mask, vector<wstring>& files) {
    HANDLE hFind = INVALID_HANDLE_VALUE;
    WIN32_FIND_DATA ffd;
    wstring spec;
    stack<wstring> directories;

    directories.push(path);
    files.clear();

    while (!directories.empty()) {
        path = directories.top();
        spec = path + L"\\" + mask;
        directories.pop();

        hFind = FindFirstFile(spec.c_str(), &ffd);
        if (hFind == INVALID_HANDLE_VALUE)  {
            return false;
        } 

        do {
            if (wcscmp(ffd.cFileName, L".") != 0 && 
                wcscmp(ffd.cFileName, L"..") != 0) {
                if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
                    directories.push(path + L"\\" + ffd.cFileName);
                }
                else {
                    files.push_back(path + L"\\" + ffd.cFileName);
                }
            }
        } while (FindNextFile(hFind, &ffd) != 0);

        if (GetLastError() != ERROR_NO_MORE_FILES) {
            FindClose(hFind);
            return false;
        }

        FindClose(hFind);
        hFind = INVALID_HANDLE_VALUE;
    }

    return true;
}
#endif
//so on and so forth.

Debe llamar a funciones específicas del sistema operativo para atravesar el sistema de archivos, como open() y readdir().El estándar C no especifica ninguna función relacionada con el sistema de archivos.

No lo haces.El C++ estándar no expone el concepto de directorio.Específicamente, no ofrece ninguna forma de enumerar todos los archivos en un directorio.

Un truco horrible sería utilizar llamadas al sistema () y analizar los resultados.La solución más razonable sería utilizar algún tipo de biblioteca multiplataforma como cuarto o incluso POSIX.

Estamos en 2019.Tenemos sistema de archivos biblioteca estándar en C++.El Filesystem library proporciona facilidades para realizar operaciones en sistemas de archivos y sus componentes, como rutas, archivos normales y directorios.

Hay una nota importante sobre este enlace si está considerando problemas de portabilidad.Dice:

Es posible que las funciones de la biblioteca del sistema de archivos no estén disponibles si la implementación no puede acceder a un sistema de archivos jerárquico o si no proporciona las capacidades necesarias.Es posible que algunas funciones no estén disponibles si no son compatibles con el sistema de archivos subyacente (p. ej.el sistema de archivos FAT carece de enlaces simbólicos y prohíbe múltiples enlaces físicos).En esos casos, se deben informar los errores.

La biblioteca del sistema de archivos se desarrolló originalmente como boost.filesystem, se publicó como la especificación técnica ISO/IEC TS 18822:2015 y finalmente se fusionó con ISO C++ a partir de C++17.La implementación de impulso está actualmente disponible en más compiladores y plataformas que la biblioteca C++17.

@ adi-shavit respondió esta pregunta cuando era parte de std::experimental y actualizó esta respuesta en 2017.Quiero dar más detalles sobre la biblioteca y mostrar un ejemplo más detallado.

std::sistema de archivos::iterador_directorio_recursivo es un LegacyInputIterator que itera sobre los elementos directorio_entry de un directorio y, de forma recursiva, sobre las entradas de todos los subdirectorios.El orden de iteración no está especificado, excepto que cada entrada del directorio se visita sólo una vez.

Si no desea iterar recursivamente sobre las entradas de los subdirectorios, entonces iterador_directorio debería ser usado.

Ambos iteradores devuelven un objeto de entrada_directorio. directory_entry tiene varias funciones miembro útiles como is_regular_file, is_directory, is_socket, is_symlink etc.El path() La función miembro devuelve un objeto de std::sistema de archivos::ruta y se puede utilizar para conseguir file extension, filename, root name.

Considere el siguiente ejemplo.he estado usando Ubuntu y lo compilé en la terminal usando

g++ ejemplo.cpp --std=c++17 -lstdc++fs -Wall

#include <iostream>
#include <string>
#include <filesystem>

void listFiles(std::string path)
{
    for (auto& dirEntry: std::filesystem::recursive_directory_iterator(path)) {
        if (!dirEntry.is_regular_file()) {
            std::cout << "Directory: " << dirEntry.path() << std::endl;
            continue;
        }
        std::filesystem::path file = dirEntry.path();
        std::cout << "Filename: " << file.filename() << " extension: " << file.extension() << std::endl;

    }
}

int main()
{
    listFiles("./");
    return 0;
}

Si está en Windows, puede usar FindFirstFile junto con la API FindNextFile.Puede utilizar FindFileData.dwFileAttributes para comprobar si una ruta determinada es un archivo o un directorio.Si es un directorio, puedes repetir el algoritmo de forma recursiva.

Aquí, he reunido un código que enumera todos los archivos en una máquina con Windows.

http://dreams-soft.com/projects/traverse-directory

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top