Pergunta

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

Foi útil?

Solução

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 um pouco sua rede, você pode querer usar 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 direto 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;
}

Outras dicas

Se estiver usando a API Win32, você pode usar o EncontrarPrimeiroArquivo e LocalizarPróximoArquivo funções.

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 FILE_ATTRIBUTE_DIRECTORY bit está definido.Se o bit estiver definido, você poderá 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 caminhos muito longos.

#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;
}

Você pode tornar isso ainda mais simples com o novo C++11 baseado em intervalo for e Impulsionar:

#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;
}

No C++ 11/14 com o "Filesystem TS", o <experimental/filesystem> cabeçalho e intervalo-for você pode simplesmente fazer isso:

#include <experimental/filesystem>

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

A partir do C++ 17, std::filesystem faz parte da biblioteca padrão e pode ser encontrado no <filesystem> cabeçalho (não mais "experimental").

Uma solução rápida é usar C's Direto.h biblioteca.

Fragmento de código funcional 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;
}

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

Tanto wxWidgets quanto Qt são estruturas C++ de plataforma cruzada de código aberto.

wxDir fornece uma maneira flexível de percorrer arquivos recursivamente usando Traverse() ou um mais simples GetAllFiles() função.Você também pode implementar a travessia com GetFirst() e GetNext() funções (presumo que Traverse() e GetAllFiles() são wrappers que eventualmente usam as funções GetFirst() e GetNext()).

QDir fornece acesso a estruturas de diretório e seu conteúdo.Existem várias maneiras de percorrer 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 exemplo de código (retirado de aqui # 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;
}

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;                                    
}

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

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

Consulte a documentação da API do seu sistema operacional para saber como fazer isso.Se você precisar ser portátil, precisará de vários #ifdefs para vários sistemas operacionais.

Você provavelmente seria melhor com o boost ou com o sistema de arquivos experimental do c++ 14. SE você está analisando um diretório interno (ou seja,usado para o seu programa armazenar dados após o programa ser fechado), então crie 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ê poderia 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.

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.

Você não.O C++ padrão não expõe o conceito de diretório.Especificamente, não é possível 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 multiplataforma, como Qt ou mesmo POSIX.

Estamos em 2019.Nós temos sistema de arquivo biblioteca padrão em C++.O Filesystem library fornece recursos para executar operações em sistemas de arquivos e seus componentes, como caminhos, arquivos regulares e diretórios.

Há uma observação importante esse link se você estiver considerando problemas 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 possui links simbólicos e proíbe vários hardlinks).Nesses casos, os erros devem ser relatados.

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

@adi-shavit respondeu a esta pergunta quando fazia parte do std::experimental e atualizou esta resposta em 2017.Quero dar mais detalhes sobre a biblioteca e mostrar exemplos mais detalhados.

std::filesystem::recursive_directory_iterator é um LegacyInputIterator que 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 de diretório é visitada apenas uma vez.

Se você não quiser iterar recursivamente nas entradas dos subdiretórios, então diretório_iterador deve ser usado.

Ambos os iteradores retornam um objeto de entrada_diretório. directory_entry tem várias funções de membro úteis, como is_regular_file, is_directory, is_socket, is_symlink etc.O path() função membro retorna um objeto de std::sistema de arquivos::caminho e pode ser usado para obter file extension, filename, root name.

Considere o exemplo abaixo.eu tenho usado Ubuntu e compilei no terminal usando

g++ exemplo.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;
}

Se você estiver no Windows, poderá 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

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top