Como você itera por meio de cada arquivo / diretório recursivamente no C ++ padrão?

115

Como você itera por meio de cada arquivo / diretório recursivamente no C ++ padrão?

Robottobor
fonte
Você pode querer examinar boost.filesystem boost.org/doc/libs/1_31_0/libs/filesystem/doc/index.htm
Doug T.
1
C ++ não padrão: pocoproject.org/docs/Poco.DirectoryIterator.html
Agnel Kurian
1
Em breve, isso deve estar no padrão por meio do TS do sistema de arquivos , com o recursive_directory_iterator
Adi Shavit de
Se o uso de uma biblioteca C padrão não atrapalhar a chamada de um programa C ++ como 'padrão', nftw () . Aqui está um exemplo
six-k
2
Alguém, que sabe o que está fazendo, deve levar uma hora para atualizar isso.
Josh C

Respostas:

99

No C ++ padrão, tecnicamente não há como fazer isso, pois o C ++ padrão não tem concepção de diretórios. Se você quiser expandir sua rede um pouco, você pode querer dar uma olhada em Boost.FileSystem . Isso foi aceito para inclusão no TR2, portanto, oferece a melhor chance de manter sua implementação o mais próximo possível do padrão.

Um exemplo, retirado diretamente do site:

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 INFORMAÇÕES
fonte
5
C ++ não tem conceito de arquivos? E quanto ao std :: fstream? Ou fopen?
Kevin
30
arquivos, não diretórios
1800 INFORMAÇÕES,
22
Atualização em relação à versão mais recente do boost: no caso de alguém se deparar com esta resposta, o boost mais recente inclui uma classe de conveniência boost :: recursive_directory_iterator, portanto, escrever o loop acima com chamada recursiva explícita não é mais necessário. Link: boost.org/doc/libs/1_46_1/libs/filesystem/v3/doc/…
JasDev
5
O VC ++ 11 vem com praticamente a mesma funcionalidade no cabeçalho <filesystem> no namespace std :: tr2 :: sys.
mheyman
3
Essa costumava ser uma boa resposta, mas agora que <filesystem> é padrão, é melhor simplesmente usá-lo (veja um exemplo nas outras respostas).
Gathar de
54

Do C ++ 17 em diante, o <filesystem>cabeçalho e o intervalo for, você pode simplesmente fazer isso:

#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 do C ++ 17, std::filesystemfaz parte da biblioteca padrão e pode ser encontrado no <filesystem>cabeçalho (não é mais "experimental").

Adi Shavit
fonte
Evite o uso de using, use em seu namespacelugar.
Roi Danton
2
E por que isto? Melhor mais específico do que trazer coisas que você não usa.
Adi Shavit
Reveja minha edição, por favor, também adicionei o padrão de namespace ausente.
Roi Danton
5
<filesystem> não é mais um TS. Faz parte do C ++ 17. Você provavelmente deve atualizar esta resposta de acordo.
Inspecionável,
Observação para usuários de mac, isso requer OSX 10.15 (Catalina) no mínimo.
Justin
45

Se estiver usando a API Win32, você pode usar as funções FindFirstFile e FindNextFile .

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

Para travessia recursiva de diretórios, você deve inspecionar cada WIN32_FIND_DATA.dwFileAttributes para verificar se o bit FILE_ATTRIBUTE_DIRECTORY está definido. Se o bit estiver definido, você pode chamar recursivamente a função com esse diretório. Alternativamente, você pode usar uma pilha para fornecer o mesmo efeito de uma chamada recursiva, mas evitando o estouro de pilha para árvores de caminho muito longas.

#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
fonte
19
quanto tempo você demorou para escrever isso? Acho que levaria menos tempo para colar C ++ ao python e fazê-lo em uma linha.
Dustin Getz
2
Esta é uma boa solução não recursiva (que às vezes é útil!).
jm.
1
A propósito, se alguém quiser editar o programa ligeiramente para aceitar um parâmetro de linha de comando argv [1] para o caminho em vez de um codificado ("F: \\ cvsrepos"), a assinatura de main (int, char) mudaria para wmain (int, wchar_t) assim: int wmain (int argc, wchar_t * argv [])
JasDev
1
Obrigado, mas esta função não funciona com o cirílico. Existe alguma maneira de fazê-lo funcionar com caracteres cirílicos como - Â, Â, Â etc?
unresolved_external
31

Você pode torná-lo ainda mais simples com o novo intervalo C ++ 11 baseado fore 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
fonte
5
Não há necessidade de impulso. O OP solicitou especificamente o c ++ padrão.
Craig B de
23

Uma solução rápida é usar a biblioteca Dirent.h do C.

Fragmento de código de trabalho da 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
fonte
5
Essa rotina não é recursiva.
user501138
@TimCooper, claro que não, dirent é específico de posix.
Vorac
1
Na verdade ele faz o trabalho em VC ++ se você receber um porto de dirent.h para C ++ visuais por Tony Rönkkö. É FOSS. Eu apenas tentei isso e funciona.
user1741137
10

Além do boost :: filesystem mencionado acima, você pode examinar wxWidgets :: wxDir e Qt :: QDir .

Ambos wxWidgets e Qt são frameworks C ++ de plataforma cruzada de código aberto.

wxDirfornece uma maneira flexível de percorrer arquivos recursivamente usando Traverse()uma GetAllFiles()função mais simples . Você também pode implementar a travessia com as funções GetFirst()e GetNext()(presumo que Traverse () e GetAllFiles () sejam invólucros que eventualmente usam as funções GetFirst () e GetNext ()).

QDirfornece acesso a estruturas de diretório e seu conteúdo. Existem várias maneiras de percorrer os diretórios com QDir. Você pode iterar sobre o conteúdo do diretório (incluindo subdiretórios) com QDirIterator que foi instanciado com o sinalizador QDirIterator :: Subdirectories. Outra maneira é usar a função GetEntryList () do QDir e implementar uma travessia recursiva.

Aqui está um código de amostra (retirado daqui # Exemplo 8-5) que mostra como iterar em todos os subdiretórios.

#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
fonte
Doxygen usa QT como sua camada de compatibilidade do sistema operacional. As ferramentas principais não usam uma GUI, apenas o material do diretório (e outros componentes).
deft_code
7

Boost :: filesystem fornece recursive_directory_iterator, que é bastante conveniente para esta tarefa:

#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
fonte
1
O que é "isso", por favor? Não há um erro de sintaxe? E como você alimenta o "fim"? (= como sabemos que analisamos todo o diretório?)
yO_
1
@yO_ você está certo, ocorreu um erro de digitação, o construtor padrão para recursive_directory_iterator irá construir um iterador "inválido", quando você terminar de iterar em dir, ele tornará "ele" se tornará inválido e será igual a "fim"
DikobrAz
5

Você pode usar ftw(3)ounftw(3) percorrer uma hierarquia de sistema de arquivos em C ou C ++ em sistemas POSIX .

Leif
fonte
github.com/six-k/dtreetrawl/blob/… tem um exemplo disso. O código faz mais algumas coisas, mas funciona como um bom tutorial de nftw()uso.
seis k
4

Você não. O padrão C ++ não tem conceito de diretórios. Depende da implementação transformar uma string em um identificador de arquivo. O conteúdo dessa string e o que ela mapeia dependem do sistema operacional. Lembre-se de que o C ++ pode ser usado para escrever esse sistema operacional, portanto, ele é usado em um nível em que a pergunta como iterar por meio de um diretório ainda não foi definida (porque você está escrevendo o código de gerenciamento de diretório).

Veja a documentação da API do seu sistema operacional para saber como fazer isso. Se você precisa ser portátil, terá que ter vários #ifdef s para vários sistemas operacionais.

Matthew Scouten
fonte
4

Você provavelmente ficaria melhor com o boost ou com o sistema de arquivos experimental do c ++ 14. SE você estiver analisando um diretório interno (ou seja, usado pelo seu programa para armazenar dados depois que o programa foi fechado), faça um arquivo de índice que tenha um índice do conteúdo do arquivo. A propósito, você provavelmente precisará usar o boost no futuro, então, se não o tiver instalado, instale-o! Em segundo lugar, você pode usar uma compilação condicional, por exemplo:

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

O código para cada caso é retirado 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
fonte
2

Você precisa chamar funções específicas do sistema operacional para travessia do sistema de arquivos, como open()e readdir(). O padrão C não especifica nenhuma função relacionada ao sistema de arquivos.

John Millikin
fonte
E o C ++? Existe alguma dessas funções no iostream?
Aaron Maenpaa,
2
Apenas para arquivos. Não existem funções do tipo "mostrar todos os arquivos de um diretório".
1800 INFORMAÇÕES,
1
@ 1800: Diretórios são arquivos.
Lightness Races in Orbit
2

Estamos em 2019. Temos a biblioteca padrão do sistema de arquivos em C++. O Filesystem libraryfornece recursos para executar operações em sistemas de arquivos e seus componentes, como caminhos, arquivos regulares e diretórios.

Há uma observação importante neste link se você estiver considerando questões de portabilidade. Diz:

Os recursos da biblioteca do sistema de arquivos podem estar indisponíveis se um sistema de arquivos hierárquico não estiver acessível para a implementação ou se não fornecer os recursos necessários. Alguns recursos podem não estar disponíveis se não forem suportados pelo sistema de arquivos subjacente (por exemplo, o sistema de arquivos FAT não tem links simbólicos e proíbe vários links físicos). Nesses casos, os erros devem ser relatados.

A biblioteca do sistema de arquivos foi originalmente desenvolvida como boost.filesystem, foi publicada como a especificação técnica ISO / IEC TS 18822: 2015 e, finalmente, foi incorporada ao ISO C ++ a partir do C ++ 17. A implementação boost está disponível atualmente em mais compiladores e plataformas do que a biblioteca C ++ 17.

@ adi-shavit respondeu a esta pergunta quando fazia parte de std :: experimental e ele atualizou esta resposta em 2017. Eu quero dar mais detalhes sobre a biblioteca e mostrar um exemplo mais detalhado.

std :: filesystem :: recursive_directory_iterator é um LegacyInputIteratorque itera sobre os elementos directory_entry de um diretório e, recursivamente, sobre as entradas de todos os subdiretórios. A ordem de iteração não é especificada, exceto que cada entrada do diretório é visitada apenas uma vez.

Se você não quiser iterar recursivamente sobre as entradas de subdiretórios, então o directory_iterator deve ser usado.

Ambos os iteradores retornam um objeto de directory_entry . directory_entrytem várias funções de membro útil, como is_regular_file, is_directory, is_socket, is_symlinketc. Os path()função de membro retorna um objeto do std :: filesystem :: caminho e ele pode ser usado para obter file extension, filename, root name.

Considere o exemplo abaixo. Tenho usado Ubuntue compilado no terminal usando

g ++ example.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
fonte
1

Você não. C ++ padrão não se expõe ao conceito de diretório. Especificamente, não fornece nenhuma maneira de listar todos os arquivos em um diretório.

Um hack horrível seria usar chamadas system () e analisar os resultados. A solução mais razoável seria usar algum tipo de biblioteca de plataforma cruzada, como Qt ou mesmo POSIX .

shoosh
fonte
1

Você pode usar std::filesystem::recursive_directory_iterator. Mas cuidado, isso inclui links simbólicos (soft). Se você quiser evitá-los, você pode usar is_symlink. Exemplo de uso:

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
fonte
1
Por último, mas não menos importante, na verdade, melhor do que as respostas anteriores.
Seyed Mehran Siadati
0

Se você estiver no Windows, pode usar FindFirstFile junto com a API FindNextFile. Você pode usar FindFileData.dwFileAttributes para verificar se um determinado caminho é um arquivo ou diretório. Se for um diretório, você pode repetir o algoritmo recursivamente.

Aqui, reuni um código que lista todos os arquivos em uma máquina Windows.

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

Ibrahim
fonte
0

A caminhada na árvore de arquivos ftwé uma forma recursiva de bloquear toda a árvore de diretórios no caminho. Mais detalhes estão aqui .

NOTA: Você também pode usar o ftsque pode pular arquivos ocultos como .ou ..ou.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;
}

a saída tem a seguinte aparência:

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 se você deseja corresponder a um nome de arquivo (exemplo: pesquisar todos os *.jpg, *.jpeg, *.pngarquivos.) Para uma necessidade específica, 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
fonte