Comment parcourez-vous chaque fichier / répertoire de manière récursive en C ++ standard?
Comment parcourez-vous chaque fichier / répertoire de manière récursive en C ++ standard?
Réponses:
En C ++ standard, techniquement, il n'y a aucun moyen de le faire puisque le C ++ standard n'a aucune conception des répertoires. Si vous souhaitez élargir un peu votre réseau, vous voudrez peut-être envisager d'utiliser Boost.FileSystem . Cela a été accepté pour inclusion dans TR2, ce qui vous donne les meilleures chances de garder votre implémentation aussi proche que possible de la norme.
Un exemple, tiré directement du site 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;
}
À partir de C ++ 17, l'en- <filesystem>
tête et la plage- for
, vous pouvez simplement faire ceci:
#include <filesystem>
using recursive_directory_iterator = std::filesystem::recursive_directory_iterator;
...
for (const auto& dirEntry : recursive_directory_iterator(myPath))
std::cout << dirEntry << std::endl;
Depuis C ++ 17, std::filesystem
fait partie de la bibliothèque standard et peut être trouvé dans l'en- <filesystem>
tête (plus "expérimental").
using
utiliser, utilisez namespace
plutôt.
Si vous utilisez l'API Win32, vous pouvez utiliser les fonctions FindFirstFile et FindNextFile .
http://msdn.microsoft.com/en-us/library/aa365200(VS.85).aspx
Pour le parcours récursif de répertoires, vous devez inspecter chaque WIN32_FIND_DATA.dwFileAttributes pour vérifier si le bit FILE_ATTRIBUTE_DIRECTORY est défini. Si le bit est défini, vous pouvez appeler la fonction de manière récursive avec ce répertoire. Vous pouvez également utiliser une pile pour fournir le même effet qu'un appel récursif mais en évitant le débordement de pile pour les arbres de chemins très longs.
#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;
}
Vous pouvez le rendre encore plus simple avec la nouvelle gamme C ++ 11 basée for
et 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;
}
Une solution rapide consiste à utiliser la bibliothèque Dirent.h de C.
Fragment de code de travail 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;
}
En plus du système de fichiers boost :: mentionné ci-dessus, vous voudrez peut-être examiner wxWidgets :: wxDir et Qt :: QDir .
WxWidgets et Qt sont des frameworks C ++ multiplateformes open source.
wxDir
fournit un moyen flexible de parcourir les fichiers de manière récursive en utilisant Traverse()
ou une GetAllFiles()
fonction plus simple . De plus, vous pouvez implémenter le parcours avec les fonctions GetFirst()
et GetNext()
(je suppose que Traverse () et GetAllFiles () sont des wrappers qui utilisent finalement les fonctions GetFirst () et GetNext ()).
QDir
donne accès aux structures de répertoires et à leur contenu. Il existe plusieurs façons de parcourir les répertoires avec QDir. Vous pouvez parcourir le contenu du répertoire (y compris les sous-répertoires) avec QDirIterator qui a été instancié avec l'indicateur QDirIterator :: Subdirectories. Une autre façon consiste à utiliser la fonction GetEntryList () de QDir et à implémenter un parcours récursif.
Voici un exemple de code (tiré d' ici # Exemple 8-5) qui montre comment parcourir tous les sous-répertoires.
#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 fournit recursive_directory_iterator, ce qui est assez pratique pour cette tâche:
#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;
}
Vous pouvez utiliser ftw(3)
ounftw(3)
pour parcourir une hiérarchie de système de fichiers en C ou C ++ sur les systèmes POSIX .
nftw()
utilisation.
Vous ne le faites pas. Le standard C ++ n'a pas de concept de répertoires. C'est à l'implémentation de transformer une chaîne en descripteur de fichier. Le contenu de cette chaîne et son mappage dépendent du système d'exploitation. Gardez à l'esprit que C ++ peut être utilisé pour écrire ce système d'exploitation, il est donc utilisé à un niveau où le fait de demander comment parcourir un répertoire n'est pas encore défini (car vous écrivez le code de gestion de répertoire).
Consultez la documentation de votre API OS pour savoir comment procéder. Si vous avez besoin d'être portable, vous devrez avoir un tas de #ifdef pour différents OS.
Vous seriez probablement mieux avec le système de fichiers expérimental de boost ou de c ++ 14. SI vous analysez un répertoire interne (c'est-à-dire utilisé par votre programme pour stocker des données après la fermeture du programme), créez un fichier d'index contenant un index du contenu du fichier. Au fait, vous devrez probablement utiliser boost à l'avenir, donc si vous ne l'avez pas installé, installez-le! Deuxièmement, vous pouvez utiliser une compilation conditionnelle, par exemple:
#ifdef WINDOWS //define WINDOWS in your code to compile for windows
#endif
Le code pour chaque cas est tiré 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.
Vous devez appeler des fonctions spécifiques au système d'exploitation pour la traversée du système de fichiers, comme open()
et readdir()
. La norme C ne spécifie aucune fonction liée au système de fichiers.
Nous sommes en 2019. Nous avons une bibliothèque standard de système de fichiers dans C++
. leFilesystem library
fournit des fonctionnalités pour effectuer des opérations sur les systèmes de fichiers et leurs composants, tels que les chemins, les fichiers normaux et les répertoires.
Il y a une note importante sur ce lien si vous envisagez des problèmes de portabilité. Ça dit:
Les fonctionnalités de la bibliothèque de système de fichiers peuvent ne pas être disponibles si un système de fichiers hiérarchique n'est pas accessible à l'implémentation ou s'il ne fournit pas les capacités nécessaires. Certaines fonctionnalités peuvent ne pas être disponibles si elles ne sont pas prises en charge par le système de fichiers sous-jacent (par exemple, le système de fichiers FAT manque de liens symboliques et interdit les liens physiques multiples). Dans ces cas, les erreurs doivent être signalées.
La bibliothèque de système de fichiers a été initialement développée en tant que boost.filesystem
, a été publiée en tant que spécification technique ISO / IEC TS 18822: 2015, et finalement fusionnée avec ISO C ++ à partir de C ++ 17. L'implémentation boost est actuellement disponible sur plus de compilateurs et de plates-formes que la bibliothèque C ++ 17.
@ adi-shavit a répondu à cette question lorsqu'elle faisait partie de std :: experimental et il a mis à jour cette réponse en 2017. Je veux donner plus de détails sur la bibliothèque et montrer un exemple plus détaillé.
std :: filesystem :: recursive_directory_iterator est un LegacyInputIterator
qui itère sur les éléments directory_entry d'un répertoire et, de manière récursive, sur les entrées de tous les sous-répertoires. L'ordre d'itération n'est pas spécifié, sauf que chaque entrée de répertoire n'est visitée qu'une seule fois.
Si vous ne voulez pas itérer récursivement sur les entrées des sous-répertoires, alors directory_iterator doit être utilisé.
Les deux itérateurs retournent un objet de type directory_entry . directory_entry
a diverses fonctions membres utiles comme is_regular_file
, is_directory
, is_socket
, is_symlink
etc. La path()
fonction membre retourne un objet de std :: :: système de fichiers chemin et il peut être utilisé pour obtenir file extension
, filename
,root name
.
Prenons l'exemple ci-dessous. Je l'ai utilisé Ubuntu
et compilé sur le terminal en utilisant
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;
}
Vous ne le faites pas. Le C ++ standard n'expose pas le concept de répertoire. Plus précisément, cela ne donne aucun moyen de lister tous les fichiers dans un répertoire.
Un horrible hack serait d'utiliser des appels system () et d'analyser les résultats. La solution la plus raisonnable serait d'utiliser une sorte de bibliothèque multiplateforme telle que Qt ou même POSIX .
Vous pouvez utiliser std::filesystem::recursive_directory_iterator
. Mais attention, cela inclut les liens symboliques (logiciels). Si vous voulez les éviter, vous pouvez utiliser is_symlink
. Exemple d'utilisation:
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;
}
Si vous êtes sous Windows, vous pouvez utiliser FindFirstFile avec l'API FindNextFile. Vous pouvez utiliser FindFileData.dwFileAttributes pour vérifier si un chemin donné est un fichier ou un répertoire. S'il s'agit d'un répertoire, vous pouvez répéter l'algorithme de manière récursive.
Ici, j'ai rassemblé un code qui répertorie tous les fichiers sur une machine Windows.
La marche dans l'arborescence des fichiers ftw
est une manière récursive de murer toute l'arborescence de répertoires dans le chemin. Plus de détails ici .
REMARQUE: vous pouvez également utiliser fts
qui peut ignorer les fichiers cachés comme .
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;
}
la sortie ressemble à ceci:
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
Disons que si vous voulez faire correspondre un nom de fichier (exemple: rechercher tous les *.jpg, *.jpeg, *.png
fichiers.) Pour un besoin spécifique, utilisez 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;
}