it-swarm.it

Come si scorre ripetutamente ogni file / directory in C ++ standard?

Come si scorre ripetutamente ogni file/directory in C++ standard?

102
robottobor

Nel C++ standard, tecnicamente non c'è modo di farlo poiché il C++ standard non ha una concezione delle directory. Se vuoi espandere un po 'la tua rete, potresti guardare usando Boost.FileSystem . Questo è stato accettato per l'inclusione in TR2, quindi ti offre le migliori possibilità di mantenere l'implementazione il più vicino possibile allo standard.

Un esempio, preso direttamente dal sito 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;
}
94
1800 INFORMATION

Se si utilizza l'API Win32 è possibile utilizzare le funzioni FindFirstFile e FindNextFile .

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

Per l'attraversamento ricorsivo di directory è necessario ispezionare ciascuno WIN32_FIND_DATA.dwFileAttributes per verificare se il FILE_ATTRIBUTE_DIRECTORY il bit è impostato. Se il bit è impostato, è possibile chiamare ricorsivamente la funzione con quella directory. In alternativa, è possibile utilizzare uno stack per fornire lo stesso effetto di una chiamata ricorsiva ma evitare lo overflow dello stack per alberi con percorsi molto lunghi.

#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;
}
42
Jorge Ferreira

Con C++ 17, il <filesystem> header e range -for, puoi semplicemente fare questo:

#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 partire da C++ 17, std::filesystem fa parte della libreria standard e si trova in <filesystem> header (non più "sperimentale").

36
Adi Shavit

Puoi renderlo ancora più semplice con il nuovo C++ 11 basato sulla gamma for e 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;
}
31
Matthieu G

Una soluzione rapida sta usando la libreria Dirent.h di C.

Frammento di codice funzionante 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;
}
23
Alex

Oltre al summenzionato boost :: filesystem potresti voler esaminare wxWidgets :: wxDir e Qt :: QDir .

Sia wxWidgets che Qt sono framework C++ open source e multipiattaforma.

wxDir fornisce un modo flessibile per attraversare i file in modo ricorsivo usando Traverse() o una funzione GetAllFiles() più semplice. Inoltre puoi implementare il traversal con le funzioni GetFirst() e GetNext() (suppongo che Traverse () e GetAllFiles () siano wrapper che alla fine usano le funzioni GetFirst () e GetNext ()).

QDir fornisce l'accesso alle strutture di directory e al loro contenuto. Esistono diversi modi per attraversare le directory con QDir. È possibile scorrere i contenuti della directory (comprese le sottodirectory) con QDirIterator che è stato istanziato con il flag QDirIterator :: Subdirectories. Un altro modo è utilizzare la funzione GetEntryList () di QDir e implementare un attraversamento ricorsivo.

Ecco un codice di esempio (preso da qui # Esempio 8-5) che mostra come scorrere tutte le sottodirectory.

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

Boost :: filesystem fornisce recursive_directory_iterator, che è abbastanza conveniente per questo compito:

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

Puoi usare ftw(3) o nftw(3) per camminare una gerarchia di filesystem in C o C++ su POSIX sistemi.

4
leif

Probabilmente saresti meglio con le cose sperimentali del filesystem boost o c ++ 14. [~ # ~] se [~ # ~] stai analizzando una directory interna (cioè usata per il tuo programma per memorizzare i dati dopo che il programma è stato chiuso ), quindi creare un file indice con un indice del contenuto del file. A proposito, probabilmente dovresti usare boost in futuro, quindi se non lo hai installato, installalo! In secondo luogo, è possibile utilizzare una compilazione condizionale, ad esempio:

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

Il codice per ciascun caso è tratto da 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.
4
ndrewxie

Non Lo standard C++ non ha il concetto di directory. Spetta all'implementazione trasformare una stringa in un handle di file. Il contenuto di quella stringa e ciò a cui mappa dipende dal sistema operativo. Tieni presente che C++ può essere utilizzato per scrivere quel sistema operativo, quindi viene utilizzato a un livello in cui non è ancora stato definito chiedere come eseguire l'iterazione attraverso una directory (perché stai scrivendo il codice di gestione della directory).

Consulta la documentazione dell'API del tuo sistema operativo per sapere come fare. Se devi essere portatile, dovrai avere un sacco di # ifdef s per vari sistemi operativi.

3
Matthew Scouten

È necessario chiamare funzioni specifiche del sistema operativo per l'attraversamento del filesystem, come open() e readdir(). Lo standard C non specifica alcuna funzione relativa al filesystem.

2
John Millikin

Non Il C++ standard non espone al concetto di directory. In particolare, non consente di elencare tutti i file in una directory.

Un trucco orribile sarebbe usare le chiamate di sistema () e analizzare i risultati. La soluzione più ragionevole sarebbe quella di utilizzare una sorta di libreria multipiattaforma come Qt o persino POSIX .

1
shoosh

Siamo nel 2019. Abbiamo filesystem libreria standard in C++. Filesystem library Fornisce funzionalità per eseguire operazioni sui file system e sui loro componenti, come percorsi, file regolari e directory.

C'è una nota importante su questo link se stai considerando problemi di portabilità. Dice:

Le funzionalità della libreria del file system potrebbero non essere disponibili se un file system gerarchico non è accessibile all'implementazione o se non fornisce le funzionalità necessarie. Alcune funzioni potrebbero non essere disponibili se non sono supportate dal file system sottostante (ad esempio, il file system FAT manca di collegamenti simbolici e proibisce collegamenti multipli multipli). In questi casi, è necessario segnalare errori.

La libreria del filesystem è stata originariamente sviluppata come boost.filesystem, È stata pubblicata come specifica tecnica ISO/IEC TS 18822: 2015 e infine unita a ISO C++ a partire da C++ 17. L'implementazione del boost è attualmente disponibile su più compilatori e piattaforme rispetto alla libreria C++ 17.

@ adi-shavit ha risposto a questa domanda quando faceva parte di std :: sperimentale e ha aggiornato questa risposta nel 2017. Voglio dare maggiori dettagli sulla biblioteca e mostrare esempi più dettagliati.

std :: filesystem :: recursive_directory_iterator è un LegacyInputIterator che scorre sugli elementi directory_entry di una directory e, ricorsivamente, sulle voci di tutte le sottodirectory. L'ordine di iterazione non è specificato, tranne per il fatto che ogni voce della directory viene visitata una sola volta.

Se non si desidera scorrere ripetutamente le voci delle sottodirectory, utilizzare directory_iterator .

Entrambi gli iteratori restituiscono un oggetto di directory_entry . directory_entry Ha varie utili funzioni membro come is_regular_file, is_directory, is_socket, is_symlink Ecc. Il membro path() La funzione restituisce un oggetto di std :: filesystem :: path e può essere usata per ottenere file extension, filename, root name.

Considera l'esempio seguente. Ho usato Ubuntu e l'ho compilato sul terminale 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;
}
0
abhiarora