web-dev-qa-db-ja.com

標準のC ++でどのようにすべてのファイル/ディレクトリを再帰的に反復処理しますか?

標準のC++でどのようにすべてのファイル/ディレクトリを再帰的に反復処理しますか?

102
robottobor

標準C++では、標準C++にはディレクトリの概念がないため、技術的にこれを行う方法はありません。ネットを少し拡張したい場合は、 Boost.FileSystem の使用を検討してください。これはTR2に含めることが認められているため、実装を可能な限り標準に近づける最良の機会が得られます。

ウェブサイトから直接取られた例:

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

Win32 APIを使用する場合は、FindFirstFileおよびFindNextFile関数を使用できます。

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

ディレクトリを再帰的にトラバースするには、各WIN32_FIND_DATA.dwFileAttributesを調べて、FILE_ATTRIBUTE_DIRECTORYビットが設定されます。ビットが設定されている場合、そのディレクトリで関数を再帰的に呼び出すことができます。または、再帰呼び出しの同じ効果を提供するためにスタックを使用できますが、非常に長いパスツリーのスタックオーバーフローを回避できます。

#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

C++ 17、 <filesystem> ヘッダー、および範囲-forを使用すると、これを簡単に行うことができます。

#include <filesystem>

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

C++ 17の時点で、std::filesystemは標準ライブラリの一部であり、<filesystem>ヘッダーにあります(「実験的」ではありません)。

36
Adi Shavit

新しい C++ 11 範囲ベースのforおよび 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

高速な解決策は、Cの Dirent.h ライブラリを使用することです。

ウィキペディアの作業コードの断片:

#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

上記のboost :: filesystemに加えて、 wxWidgets :: wxDir および Qt :: QDir を調べることもできます。

WxWidgetsとQtはどちらもオープンソースのクロスプラットフォームC++フレームワークです。

wxDirは、Traverse()またはより単純なGetAllFiles()関数を使用して、ファイルを再帰的にトラバースする柔軟な方法を提供します。同様に、GetFirst()およびGetNext()関数を使用してトラバーサルを実装できます(Traverse()およびGetAllFiles()は、最終的にGetFirst()およびGetNext()関数を使用するラッパーであると想定しています)。

QDirは、ディレクトリ構造とそのコンテンツへのアクセスを提供します。 QDirでディレクトリをトラバースするにはいくつかの方法があります。 QDirIterator :: Subdirectoriesフラグでインスタンス化されたQDirIteratorを使用して、ディレクトリの内容(サブディレクトリを含む)を反復処理できます。別の方法は、QDirのGetEntryList()関数を使用して、再帰的なトラバーサルを実装することです。

すべてのサブディレクトリを反復処理する方法を示すサンプルコード( here #例8-5から取得)です。

#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はrecursive_directory_iteratorを提供します。これはこのタスクに非常に便利です:

#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

ftw(3)またはnftw(3) を使用して、 POSIX システム上のCまたはC++のファイルシステム階層をウォークできます。

4
leif

おそらく、boostまたはc ++ 14の実験的なファイルシステムのいずれかが最適です。 が内部ディレクトリを解析している場合(つまり、プログラムが閉じられた後にデータを保存するためにプログラムに使用される場合)、インデックスファイルを作成します。ファイルの内容のインデックスがあります。ちなみに、将来的にはおそらくboostを使用する必要があるため、インストールしていない場合はインストールしてください。次に、条件付きコンパイルを使用できます。例:

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

各ケースのコードは 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

あなたはしません。 C++標準には、ディレクトリの概念はありません。文字列をファイルハンドルに変換するのは実装次第です。その文字列の内容とマッピング先はOSに依存します。 C++を使用してそのOSを作成できるため、ディレクトリを反復処理する方法がまだ定義されていないレベルで使用されることに注意してください(ディレクトリ管理コードを記述しているため)。

これを行う方法については、OS APIドキュメントをご覧ください。移植性が必要な場合は、さまざまなOSに対して #ifdef sの束が必要になります。

3
Matthew Scouten

open()readdir()など、ファイルシステムのトラバーサルのためにOS固有の関数を呼び出す必要があります。 C標準では、ファイルシステム関連の機能は指定されていません。

2
John Millikin

あなたはしません。標準C++は、ディレクトリの概念を公開しません。特に、ディレクトリ内のすべてのファイルをリストする方法はありません。

恐ろしいハックは、system()呼び出しを使用して結果を解析することです。最も合理的な解決策は、 QtPOSIX などのクロスプラットフォームライブラリを使用することです。

1
shoosh

2019年です。 filesystemC++に標準ライブラリがあります。 Filesystem libraryは、パス、通常のファイル、ディレクトリなど、ファイルシステムとそのコンポーネントで操作を実行するための機能を提供します。

移植性の問題を検討している場合は、 このリンク に重要な注意事項があります。それは言います:

階層ファイルシステムが実装にアクセスできない場合、または必要な機能を提供しない場合、ファイルシステムライブラリ機能は利用できない場合があります。基礎となるファイルシステムでサポートされていない場合、一部の機能が利用できない場合があります(たとえば、FATファイルシステムにシンボリックリンクがなく、複数のハードリンクが禁止されています)。これらの場合、エラーを報告する必要があります。

ファイルシステムライブラリは元々boost.filesystemとして開発され、技術仕様ISO/IEC TS 18822:2015として公開され、最終的にC++ 17の時点でISO C++にマージされました。ブースト実装は現在、C++ 17ライブラリよりも多くのコンパイラとプラットフォームで利用できます。

@ adi-shavitはstd :: experimentalの一部であるときにこの質問に回答し、2017年にこの回答を更新しました。ライブラリについてさらに詳しく説明し、より詳細な例を示します。

std :: filesystem :: recursive_directory_iteratorLegacyInputIteratorであり、ディレクトリのdirectory_entry要素、および再帰的にすべてのサブディレクトリのエントリを反復処理します。各ディレクトリエントリが1回だけアクセスされることを除いて、反復順序は指定されていません。

サブディレクトリのエントリを再帰的に繰り返したくない場合は、 directory_iterator を使用する必要があります。

両方の反復子は、 directory_entry のオブジェクトを返します。 directory_entryには、is_regular_fileis_directoryis_socketis_symlinkなどのさまざまな便利なメンバー関数があります。path()メンバー関数は、次のオブジェクトを返します。 std :: filesystem :: path そして、file extensionfilenameroot nameの取得に使用できます。

以下の例を考えてください。私はUbuntuを使用しており、端末上でそれをコンパイルしました

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