¿Cómo recorre cada archivo / directorio de forma recursiva en C ++ estándar?

115

¿Cómo recorre cada archivo / directorio de forma recursiva en C ++ estándar?

robottobor
fuente
Es posible que desee examinar boost.filesystem boost.org/doc/libs/1_31_0/libs/filesystem/doc/index.htm
Doug T.
1
C ++ no estándar: pocoproject.org/docs/Poco.DirectoryIterator.html
Agnel Kurian
1
Esto pronto debería estar en el estándar a través de Filesystem TS , con recursive_directory_iterator
Adi Shavit
Si el uso de una biblioteca C estándar no se interpone en la forma de llamar a un programa C ++ como 'estándar', nftw () . Aquí hay un ejemplo
six-k
2
Alguien, que sepa lo que está haciendo, debería tomar una hora para actualizar esto.
Josh C

Respuestas:

99

En C ++ estándar, técnicamente no hay forma de hacer esto ya que C ++ estándar no tiene un concepto de directorios. Si desea expandir su red un poco, le recomendamos que use Boost.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 cercana posible al 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;
}
1800 INFORMACIÓN
fuente
5
¿C ++ no tiene concepto de archivos? ¿Qué pasa con std :: fstream? ¿O fopen?
Kevin
30
archivos, no directorios
1800 INFORMACIÓN
22
Actualización con respecto a la última versión de impulso: en caso de que alguien se encuentre con esta respuesta, el último impulso incluye una clase de conveniencia boost :: recursive_directory_iterator, por lo que ya no es necesario escribir el bucle anterior con una llamada recursiva explícita. Enlace: boost.org/doc/libs/1_46_1/libs/filesystem/v3/doc/…
JasDev
5
VC ++ 11 viene con la misma funcionalidad en el encabezado <filesystem> bajo el espacio de nombres std :: tr2 :: sys.
mheyman
3
Esta solía ser una buena respuesta, pero ahora que <filesystem> es estándar, es mejor simplemente usar is (vea otras respuestas para ver un ejemplo).
Gathar
54

Desde C ++ 17 en adelante, el <filesystem>encabezado y el rango for, simplemente puede hacer esto:

#include <filesystem>

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

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

Adi Shavit
fuente
Evite el uso de using, use namespaceen su lugar.
Roi Danton
2
¿Y por qué es eso? Mejor más específico que traer cosas que no usas.
Adi Shavit
Revise mi edición, por favor, también agregué el espacio de nombres faltante std.
Roi Danton
5
<filesystem> ya no es un TS. Es parte de C ++ 17. Probablemente debería actualizar esta respuesta en consecuencia.
Inspectable
Nota para los usuarios de Mac, esto requiere OSX 10.15 (Catalina) como mínimo.
Justin
45

Si usa la API de Win32, puede usar las funciones FindFirstFile y FindNextFile .

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

Para el recorrido recursivo de directorios, debe inspeccionar cada WIN32_FIND_DATA.dwFileAttributes para verificar si el bit FILE_ATTRIBUTE_DIRECTORY está establecido. Si el bit está establecido, puede llamar de forma recursiva 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;
}
Jorge Ferreira
fuente
19
¿Cuánto tiempo te llevó escribir eso? Creo que tomaría menos tiempo pegar C ++ a Python y hacerlo en una línea.
Dustin Getz
2
Esta es una buena solución no recursiva (¡que a veces es útil!).
jm.
1
Por cierto, si alguien quiere editar el programa ligeramente para aceptar un parámetro de línea de comando argv [1] para la ruta en lugar de uno codificado ("F: \\ cvsrepos"), la firma de main (int, char) cambiaría a wmain (int, wchar_t) así: int wmain (int argc, wchar_t * argv [])
JasDev
1
Gracias, pero esta función no funciona con Cyrilic. ¿Hay alguna forma de hacer que funcione con caracteres cirílicos como - б, в, г, etc.?
unresolved_external
31

Puede hacerlo aún más simple con el nuevo rango C ++ 11 basado fory Boost :

#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;
}
Matthieu G
fuente
5
No necesita impulso. El OP solicitó específicamente c ++ estándar.
Craig B
23

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

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;
}
Alex
fuente
5
Esta rutina no es recursiva.
user501138
@TimCooper, por supuesto que no, dirent es específico de posix.
Vorac
1
En realidad hace el trabajo en VC ++ si se obtiene un puerto de dirent.h para C ++ visual por Tony Rönkkö. Es FOSS. Intenté esto y funciona.
user1741137
10

Además del sistema de archivos boost :: mencionado anteriormente, es posible que desee examinar wxWidgets :: wxDir y Qt :: QDir .

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

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

QDirproporciona acceso a las estructuras de directorios y su contenido. Hay varias formas de recorrer directorios con QDir. Puede iterar sobre el contenido del directorio (incluidos los subdirectorios) con QDirIterator que se instancia con el indicador QDirIterator :: Subdirectories. Otra forma es usar 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;
}
mrvincenzo
fuente
Doxygen utiliza QT como capa de compatibilidad del sistema operativo. Las herramientas principales no usan una GUI en absoluto, solo las cosas del directorio (y otros componentes).
deft_code
7

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;                                    
}
DikobrAz
fuente
1
¿Qué es "eso" por favor? ¿No hay un error de sintaxis? ¿Y cómo se alimenta el "final"? (= ¿cómo saber que analizamos todo el directorio?)
yO_
1
@yO_ tienes razón, hubo un error tipográfico, el constructor predeterminado para recursive_directory_iterator construirá un iterador "no válido", cuando termines de iterar sobre dir, "eso" se volverá inválido y será igual a "end"
DikobrAz
4

Tu no 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 lo que 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 #ifdef s para varios sistemas operativos.

Matthew Scouten
fuente
4

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 por su programa para almacenar datos después de que se cerró el programa), entonces cree un archivo de índice que tenga un índice del contenido del archivo. Por cierto, probablemente necesitará usar boost en el futuro, así que si no lo tiene instalado, ¡instálelo! En segundo lugar, podría usar 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.
ndrewxie
fuente
2

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

John Millikin
fuente
¿Y C ++? ¿Existen tales funciones en iostream?
Aaron Maenpaa
2
Solo para archivos. No hay ningún tipo de función "muéstrame todos los archivos en un directorio".
1800 INFORMACIÓN
1
@ 1800: los directorios son archivos.
Lightness Races in Orbit
2

Estamos en 2019. Tenemos la biblioteca estándar del sistema de archivos en formato C++. losFilesystem library proporciona facilidades para la realización de operaciones en sistemas de archivos y sus componentes, tales como caminos, archivos regulares y directorios.

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

Es posible que las instalaciones de la biblioteca del sistema de archivos no estén disponibles si la implementación no tiene acceso a un sistema de archivos jerárquico o si no proporciona las capacidades necesarias. Algunas funciones pueden no estar disponibles si no son compatibles con el sistema de archivos subyacente (por ejemplo, el sistema de archivos FAT carece de enlaces simbólicos y prohíbe varios 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 boost está disponible actualmente en más compiladores y plataformas que la biblioteca C ++ 17.

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

std :: filesystem :: recursive_directory_iterator es un elemento LegacyInputIteratorque itera sobre los elementos directory_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 de directorio se visita solo una vez.

Si no desea iterar de forma recursiva sobre las entradas de los subdirectorios, debe utilizar directory_iterator .

Ambos iteradores devuelven un objeto de directory_entry . directory_entrytiene varias funciones miembro útil como is_regular_file, is_directory, is_socket, is_symlinketc. Las path()funciones miembro devuelve un objeto de std :: sistema de archivos :: trayectoria y que puede ser utilizado para obtener file extension, filename, root name.

Considere el siguiente ejemplo. Lo he estado usando Ubuntuy compilado 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;
}
abhiarora
fuente
1

Tu no C ++ estándar no se expone al concepto de directorio. Específicamente, no da ninguna forma de listar todos los archivos en un directorio.

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

shoosh
fuente
1

Puede utilizar std::filesystem::recursive_directory_iterator. Pero tenga cuidado, esto incluye enlaces simbólicos (suaves). Si quieres evitarlos puedes usar is_symlink. Uso de ejemplo:

size_t directorySize(const std::filesystem::path& directory)
{
    size_t size{ 0 };
    for (const auto& entry : std::filesystem::recursive_directory_iterator(directory))
    {
        if (entry.is_regular_file() && !entry.is_symlink())
        {
            size += entry.file_size();
        }
    }
    return size;
}
pooya13
fuente
1
Por último, pero no menos importante, en realidad es mejor que las respuestas anteriores.
Seyed Mehran Siadati
0

Si está en Windows, puede usar FindFirstFile junto con FindNextFile API. Puede utilizar FindFileData.dwFileAttributes para comprobar si una ruta determinada es un archivo o un directorio. Si es un directorio, puede 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

Ibrahim
fuente
0

La caminata por el árbol de archivos ftwes una forma recursiva de crear un muro en todo el árbol de directorios en la ruta. Más detalles están aquí .

NOTA: También puede usar ftsque puede omitir archivos ocultos como .o ..o.bashrc

#include <ftw.h>
#include <stdio.h>
#include <sys/stat.h>
#include <string.h>

 
int list(const char *name, const struct stat *status, int type)
{
     if (type == FTW_NS)
     {
         return 0;
     }

     if (type == FTW_F)
     {
         printf("0%3o\t%s\n", status->st_mode&0777, name);
     }

     if (type == FTW_D && strcmp(".", name) != 0)
     {
         printf("0%3o\t%s/\n", status->st_mode&0777, name);
     }
     return 0;
}

int main(int argc, char *argv[])
{
     if(argc == 1)
     {
         ftw(".", list, 1);
     }
     else
     {
         ftw(argv[1], list, 1);
     }

     return 0;
}

la salida se parece a lo siguiente:

0755    ./Shivaji/
0644    ./Shivaji/20200516_204454.png
0644    ./Shivaji/20200527_160408.png
0644    ./Shivaji/20200527_160352.png
0644    ./Shivaji/20200520_174754.png
0644    ./Shivaji/20200520_180103.png
0755    ./Saif/
0644    ./Saif/Snapchat-1751229005.jpg
0644    ./Saif/Snapchat-1356123194.jpg
0644    ./Saif/Snapchat-613911286.jpg
0644    ./Saif/Snapchat-107742096.jpg
0755    ./Milind/
0644    ./Milind/IMG_1828.JPG
0644    ./Milind/IMG_1839.JPG
0644    ./Milind/IMG_1825.JPG
0644    ./Milind/IMG_1831.JPG
0644    ./Milind/IMG_1840.JPG

Digamos que si desea hacer coincidir un nombre de archivo (ejemplo: buscar todos los *.jpg, *.jpeg, *.pngarchivos) para necesidades específicas, use fnmatch.

 #include <ftw.h>
 #include <stdio.h>
 #include <sys/stat.h>
 #include <iostream>
 #include <fnmatch.h>

 static const char *filters[] = {
     "*.jpg", "*.jpeg", "*.png"
 };

 int list(const char *name, const struct stat *status, int type)
 {
     if (type == FTW_NS)
     {
         return 0;
     }

     if (type == FTW_F)
     {
         int i;
         for (i = 0; i < sizeof(filters) / sizeof(filters[0]); i++) {
             /* if the filename matches the filter, */
             if (fnmatch(filters[i], name, FNM_CASEFOLD) == 0) {
                 printf("0%3o\t%s\n", status->st_mode&0777, name);
                 break;
             }
         }
     }

     if (type == FTW_D && strcmp(".", name) != 0)
     {
         //printf("0%3o\t%s/\n", status->st_mode&0777, name);
     }
     return 0;
 }

 int main(int argc, char *argv[])
 {
     if(argc == 1)
     {
         ftw(".", list, 1);
     }
     else
     {
         ftw(argv[1], list, 1);
     }

     return 0;
 }
Milind Deore
fuente