it-swarm.com.ru

Как я могу получить список файлов в каталоге, используя C или C++?

Как я могу определить список файлов в каталоге из моего кода C или C++?

Мне не разрешено выполнять команду ls и анализировать результаты из моей программы.

489
samoz

В небольших и простых задачах я не использую boost, я использую dirent.h, который также доступен для windows:

DIR *dir;
struct dirent *ent;
if ((dir = opendir ("c:\\src\\")) != NULL) {
  /* print all the files and directories within directory */
  while ((ent = readdir (dir)) != NULL) {
    printf ("%s\n", ent->d_name);
  }
  closedir (dir);
} else {
  /* could not open directory */
  perror ("");
  return EXIT_FAILURE;
}

Это всего лишь небольшой заголовочный файл, который выполняет большинство простых задач без использования большого подхода на основе шаблонов, такого как boost (без обид, мне нравится boost!).

Автор слоя совместимости окон - Тони Ронкко. В Unix это стандартный заголовок.

ОБНОВЛЕНИЕ 2017:

В C++ 17 теперь есть официальный способ перечисления файлов вашей файловой системы: std::filesystem. Существует отличный ответ от Shreevardhan ниже с этим исходным кодом:

#include <string>
#include <iostream>
#include <filesystem>
namespace fs = std::filesystem;

int main()
{
    std::string path = "/path/to/directory";
    for (const auto & entry : fs::directory_iterator(path))
        std::cout << entry.path() << std::endl;
}
686
Peter Parker

C++ 17 теперь имеет std::filesystem::directory_iterator , который можно использовать как

#include <string>
#include <iostream>
#include <filesystem>
namespace fs = std::filesystem;

int main()
{
    std::string path = "/path/to/directory";
    for (const auto & entry : fs::directory_iterator(path))
        std::cout << entry.path() << std::endl;
}

Кроме того, std::filesystem::recursive_directory_iterator также может перебирать подкаталоги.

236
Shreevardhan

К сожалению, стандарт C++ не определяет стандартный способ работы с файлами и папками таким образом. 

Поскольку кроссплатформенного способа не существует, лучшим кроссплатформенным способом является использование библиотеки, такой как модуль файловой системы boost .

Кроссплатформенный метод повышения:

Следующая функция, учитывая путь к каталогу и имя файла, рекурсивно ищет в каталоге и его подкаталогах имя файла, возвращая bool, а в случае успеха - путь к найденному файлу. 

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

Источник со страницы поддержки, упомянутой выше.


Для систем на основе Unix/Linux: 

Вы можете использовать opendir / readdir / closedir

Пример кода, который ищет в каталоге запись `` name '':

   len = strlen(name);
   dirp = opendir(".");
   while ((dp = readdir(dirp)) != NULL)
           if (dp->d_namlen == len && !strcmp(dp->d_name, name)) {
                   (void)closedir(dirp);
                   return FOUND;
           }
   (void)closedir(dirp);
   return NOT_FOUND;

Исходный код с вышеупомянутых man-страниц.


Для систем на базе Windows: 

вы можете использовать Win32 API FindFirstFile / FindNextFile / FindClose функции.

В следующем примере C++ показано минимальное использование FindFirstFile.

#include <windows.h>
#include <tchar.h>
#include <stdio.h>

void _tmain(int argc, TCHAR *argv[])
{
   WIN32_FIND_DATA FindFileData;
   HANDLE hFind;

   if( argc != 2 )
   {
      _tprintf(TEXT("Usage: %s [target_file]\n"), argv[0]);
      return;
   }

   _tprintf (TEXT("Target file is %s\n"), argv[1]);
   hFind = FindFirstFile(argv[1], &FindFileData);
   if (hFind == INVALID_HANDLE_VALUE) 
   {
      printf ("FindFirstFile failed (%d)\n", GetLastError());
      return;
   } 
   else 
   {
      _tprintf (TEXT("The first file found is %s\n"), 
                FindFileData.cFileName);
      FindClose(hFind);
   }
}

Исходный код с указанных выше страниц MSDN.

219
Brian R. Bondy

Достаточно одной функции, вам не нужно использовать какую-либо стороннюю библиотеку (для Windows).

#include <Windows.h>

vector<string> get_all_files_names_within_folder(string folder)
{
    vector<string> names;
    string search_path = folder + "/*.*";
    WIN32_FIND_DATA fd; 
    HANDLE hFind = ::FindFirstFile(search_path.c_str(), &fd); 
    if(hFind != INVALID_HANDLE_VALUE) { 
        do { 
            // read all (real) files in current folder
            // , delete '!' read other 2 default folder . and ..
            if(! (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ) {
                names.Push_back(fd.cFileName);
            }
        }while(::FindNextFile(hFind, &fd)); 
        ::FindClose(hFind); 
    } 
    return names;
}

PS: как упомянул @Sebastian, вы можете изменить *.* на *.ext, чтобы получать только EXT-файлы (т.е. определенного типа) в этом каталоге.

75
herohuyongtao

Для решения C only, пожалуйста, проверьте это. Требуется только дополнительный заголовок:

https://github.com/cxong/tinydir

tinydir_dir dir;
tinydir_open(&dir, "/path/to/dir");

while (dir.has_next)
{
    tinydir_file file;
    tinydir_readfile(&dir, &file);

    printf("%s", file.name);
    if (file.is_dir)
    {
        printf("/");
    }
    printf("\n");

    tinydir_next(&dir);
}

tinydir_close(&dir);

Некоторые преимущества перед другими вариантами:

  • Это портативно - переносит POSIX dirent и Windows FindFirstFile
  • Он использует readdir_r там, где он доступен, что означает (как правило) потокобезопасность
  • Поддерживает Windows UTF-16 через те же макросы UNICODE
  • Это C90, так что даже очень древние компиляторы могут использовать его
46
congusbongus

Я рекомендую использовать glob с этой многоразовой оболочкой. Он генерирует vector<string>, соответствующий путям к файлам, которые соответствуют шаблону glob:

#include <glob.h>
#include <vector>
using std::vector;

vector<string> globVector(const string& pattern){
    glob_t glob_result;
    glob(pattern.c_str(),GLOB_TILDE,NULL,&glob_result);
    vector<string> files;
    for(unsigned int i=0;i<glob_result.gl_pathc;++i){
        files.Push_back(string(glob_result.gl_pathv[i]));
    }
    globfree(&glob_result);
    return files;
}

Который затем может быть вызван с обычным системным шаблоном шаблона, таким как:

vector<string> files = globVector("./*");
27
Chris Redford

Вот очень простой код в C++11, использующий библиотеку boost::filesystem для получения имен файлов в каталоге (исключая имена папок):

#include <string>
#include <iostream>
#include <boost/filesystem.hpp>
using namespace std;
using namespace boost::filesystem;

int main()
{
    path p("D:/AnyFolder");
    for (auto i = directory_iterator(p); i != directory_iterator(); i++)
    {
        if (!is_directory(i->path())) //we eliminate directories
        {
            cout << i->path().filename().string() << endl;
        }
        else
            continue;
    }
}

Вывод как:

file1.txt
file2.dat
21
Bad

Почему бы не использовать glob()?

#include <glob.h>

glob_t glob_result;
glob("/your_directory/*",GLOB_TILDE,NULL,&glob_result);
for(unsigned int i=0; i<glob_result.gl_pathc; ++i){
  cout << glob_result.gl_pathv[i] << endl;
}
17
Meekohi

Я думаю, ниже фрагмент может быть использован для перечисления всех файлов.

#include <stdio.h>
#include <dirent.h>
#include <sys/types.h>

static void list_dir(const char *path)
{
    struct dirent *entry;
    DIR *dir = opendir(path);
    if (dir == NULL) {
        return;
    }

    while ((entry = readdir(dir)) != NULL) {
        printf("%s\n",entry->d_name);
    }

    closedir(dir);
}

Ниже приводится структура struct dirent

struct dirent {
    ino_t d_ino; /* inode number */
    off_t d_off; /* offset to the next dirent */
    unsigned short d_reclen; /* length of this record */
    unsigned char d_type; /* type of file */
    char d_name[256]; /* filename */
};
16
Shrikant

Попробуйте повысить для метода X-платформы

http://www.boost.org/doc/libs/1_38_0/libs/filesystem/doc/index.htm

или просто используйте файлы, специфичные для вашей ОС.

10
Tim

Проверьте этот класс, который использует Win32 API. Просто создайте экземпляр, указав foldername, из которого вы хотите получить листинг, затем вызовите метод getNextFile, чтобы получить следующую filename из каталога. Я думаю, что нужны windows.h и stdio.h.

class FileGetter{
    WIN32_FIND_DATAA found; 
    HANDLE hfind;
    char folderstar[255];       
    int chk;

public:
    FileGetter(char* folder){       
        sprintf(folderstar,"%s\\*.*",folder);
        hfind = FindFirstFileA(folderstar,&found);
        //skip .
        FindNextFileA(hfind,&found);        
    }

    int getNextFile(char* fname){
        //skips .. when called for the first time
        chk=FindNextFileA(hfind,&found);
        if (chk)
            strcpy(fname, found.cFileName);     
        return chk;
    }

};
8
robertvarga

GNU Manual FTW

http://www.gnu.org/software/libc/manual/html_node/Simple-Directory-Lister.html#Simple-Directory-Lister

Кроме того, иногда хорошо идти прямо к источнику (каламбур). Вы можете многому научиться, взглянув на внутренности некоторых наиболее распространенных команд в Linux. Я установил простое зеркало GNU Coreutils на GitHub (для чтения).

https://github.com/homer6/gnu_coreutils/blob/master/src/ls.c

Возможно, это не относится к Windows, но с помощью этих методов может быть несколько случаев использования вариантов Unix.

Надеюсь, это поможет...

6
Homer6
char **getKeys(char *data_dir, char* tablename, int *num_keys)
{
    char** arr = malloc(MAX_RECORDS_PER_TABLE*sizeof(char*));
int i = 0;
for (;i < MAX_RECORDS_PER_TABLE; i++)
    arr[i] = malloc( (MAX_KEY_LEN+1) * sizeof(char) );  


char *buf = (char *)malloc( (MAX_KEY_LEN+1)*sizeof(char) );
snprintf(buf, MAX_KEY_LEN+1, "%s/%s", data_dir, tablename);

DIR* tableDir = opendir(buf);
struct dirent* getInfo;

readdir(tableDir); // ignore '.'
readdir(tableDir); // ignore '..'

i = 0;
while(1)
{


    getInfo = readdir(tableDir);
    if (getInfo == 0)
        break;
    strcpy(arr[i++], getInfo->d_name);
}
*(num_keys) = i;
return arr;
}
4
JasonYen2205

Я надеюсь, что этот код поможет вам.

#include <windows.h>
#include <iostream>
#include <string>
#include <vector>
using namespace std;

string wchar_t2string(const wchar_t *wchar)
{
    string str = "";
    int index = 0;
    while(wchar[index] != 0)
    {
        str += (char)wchar[index];
        ++index;
    }
    return str;
}

wchar_t *string2wchar_t(const string &str)
{
    wchar_t wchar[260];
    int index = 0;
    while(index < str.size())
    {
        wchar[index] = (wchar_t)str[index];
        ++index;
    }
    wchar[index] = 0;
    return wchar;
}

vector<string> listFilesInDirectory(string directoryName)
{
    WIN32_FIND_DATA FindFileData;
    wchar_t * FileName = string2wchar_t(directoryName);
    HANDLE hFind = FindFirstFile(FileName, &FindFileData);

    vector<string> listFileNames;
    listFileNames.Push_back(wchar_t2string(FindFileData.cFileName));

    while (FindNextFile(hFind, &FindFileData))
        listFileNames.Push_back(wchar_t2string(FindFileData.cFileName));

    return listFileNames;
}

void main()
{
    vector<string> listFiles;
    listFiles = listFilesInDirectory("C:\\*.txt");
    for each (string str in listFiles)
        cout << str << endl;
}
3
Yas

вы можете получить все прямые файлы в вашем корневом каталоге, используя std :: эксперимент :: filesystem :: directory_iterator (). Затем прочитайте имя этих файлов.

#include <iostream>
#include <filesystem>
#include <string>
#include <direct.h>
using namespace std;
namespace fs = std::experimental::filesystem;
void ShowListFile(string path)
{
for(auto &p: fs::directory_iterator(path))  /*get directory */
     cout<<p.path().filename()<<endl;   // get file name
}

int main() {

ShowListFile("C:/Users/Dell/Pictures/Camera Roll/");
getchar();
return 0;
}
2
ducPham

Это работает для меня. Извините, если не могу вспомнить источник. Это, вероятно, со страницы руководства.

#include <ftw.h>

int AnalizeDirectoryElement (const char *fpath, 
                            const struct stat *sb,
                            int tflag, 
                            struct FTW *ftwbuf) {

  if (tflag == FTW_F) {
    std::string strFileName(fpath);

    DoSomethingWith(strFileName);
  }
  return 0; 
}

void WalkDirectoryTree (const char * pchFileName) {

  int nFlags = 0;

  if (nftw(pchFileName, AnalizeDirectoryElement, 20, nFlags) == -1) {
    perror("nftw");
  }
}

int main() {
  WalkDirectoryTree("some_dir/");
}
2
ENHering

Shreevardhan ответ отлично работает. Но если вы хотите использовать его в C++ 14, просто внесите изменения namespace fs = experimental::filesystem;

т.е.

#include <string>
#include <iostream>
#include <filesystem>

using namespace std;
namespace fs = experimental::filesystem;

int main()
{
    string path = "C:\\splits\\";
    for (auto & p : fs::directory_iterator(path))
        cout << p << endl;
    int n;
    cin >> n;
}
2
Venkat Vinay

Эта реализация реализует вашу цель, динамически заполняя массив строк содержимым указанного каталога.

int exploreDirectory(const char *dirpath, char ***list, int *numItems) {
    struct dirent **direntList;
    int i;
    errno = 0;

    if ((*numItems = scandir(dirpath, &direntList, NULL, alphasort)) == -1)
        return errno;

    if (!((*list) = malloc(sizeof(char *) * (*numItems)))) {
        fprintf(stderr, "Error in list allocation for file list: dirpath=%s.\n", dirpath);
        exit(EXIT_FAILURE);
    }

    for (i = 0; i < *numItems; i++) {
        (*list)[i] = stringDuplication(direntList[i]->d_name);
    }

    for (i = 0; i < *numItems; i++) {
        free(direntList[i]);
    }

    free(direntList);

    return 0;
}
2
Giacomo Marciani

Этот ответ должен работать для пользователей Windows, которые столкнулись с трудностями при работе с Visual Studio с любыми другими ответами.

  1. Загрузите файл dirent.h со страницы github. Но лучше просто использовать файл Raw dirent.h и следовать моим инструкциям ниже (именно так я его и заработал).

    Страница Github для dirent.h для Windows: Страница Github для dirent.h

    Файл Raw Dirent: Файл Raw dirent.h

  2. Перейти к вашему проекту и добавить новый элемент (Ctrl+Shift+A). Добавьте файл заголовка (.h) и назовите его dirent.h.

  3. Вставьте код Raw dirent.h File в свой заголовок.

  4. Включите "dirent.h" в свой код.

  5. Поместите приведенный ниже метод void filefinder() в свой код и вызовите его из функции main или отредактируйте функцию так, как вы хотите ее использовать.

    #include <stdio.h>
    #include <string.h>
    #include "dirent.h"
    
    string path = "C:/folder"; //Put a valid path here for folder
    
    void filefinder()
    {
        DIR *directory = opendir(path.c_str());
        struct dirent *direntStruct;
    
        if (directory != NULL) {
            while (direntStruct = readdir(directory)) {
                printf("File Name: %s\n", direntStruct->d_name); //If you are using <stdio.h>
                //std::cout << direntStruct->d_name << std::endl; //If you are using <iostream>
            }
        }
        closedir(directory);
    }
    
1
ZKR

Системный вызов это!

system( "dir /b /s /a-d * > file_names.txt" );

Тогда просто прочитайте файл.

Правка: Этот ответ следует считать взломать, но он действительно работает (хотя и в зависимости от платформы), если у вас нет доступа к более элегантным решениям.

1
Catalyst

Я попытался следовать примеру, приведенному в обоихanswers , и, возможно, стоит отметить, что похоже, что std::filesystem::directory_entry был изменен, чтобы не иметь перегрузки оператора <<. Вместо std::cout << p << std::endl; я должен был использовать следующее, чтобы иметь возможность скомпилировать и заставить его работать:

#include <iostream>
#include <filesystem>
#include <string>
namespace fs = std::filesystem;

int main() {
    std::string path = "/path/to/directory";
    for(const auto& p : fs::directory_iterator(path))
        std::cout << p.path() << std::endl;
}

попытка передать p самостоятельно в std::cout << привела к отсутствующей ошибке перегрузки.

0
StarKiller4011

Поскольку файлы и подкаталоги каталога обычно хранятся в древовидной структуре, интуитивно понятным способом является использование алгоритма DFS для рекурсивного обхода каждого из них. Вот пример в операционной системе Windows с использованием основных файловых функций в io.h. Вы можете заменить эти функции на другой платформе. Что я хочу выразить, так это то, что основная идея DFS полностью соответствует этой проблеме. 

#include<io.h>
#include<iostream.h>
#include<string>
using namespace std;

void TraverseFilesUsingDFS(const string& folder_path){
   _finddata_t file_info;
   string any_file_pattern = folder_path + "\\*";
   intptr_t handle = _findfirst(any_file_pattern.c_str(),&file_info);
   //If folder_path exsist, using any_file_pattern will find at least two files "." and "..", 
   //of which "." means current dir and ".." means parent dir
   if (handle == -1){
       cerr << "folder path not exist: " << folder_path << endl;
       exit(-1);
   }
   //iteratively check each file or sub_directory in current folder
   do{
       string file_name=file_info.name; //from char array to string
       //check whtether it is a sub direcotry or a file
       if (file_info.attrib & _A_SUBDIR){
            if (file_name != "." && file_name != ".."){
               string sub_folder_path = folder_path + "\\" + file_name;                
               TraverseFilesUsingDFS(sub_folder_path);
               cout << "a sub_folder path: " << sub_folder_path << endl;
            }
       }
       else
            cout << "file name: " << file_name << endl;
    } while (_findnext(handle, &file_info) == 0);
    //
    _findclose(handle);
}
0
tkingcer

Просто то, чем я хочу поделиться и поблагодарить вас за материал для чтения. Поиграйте с функцией, чтобы немного ее понять. Вам может понравиться это. e обозначает расширение, p обозначает путь, а s обозначает разделитель пути. 

Если путь пройден без конечного разделителя, разделитель будет добавлен к пути. Для расширения, если введена пустая строка, функция вернет любой файл, который не имеет расширения в своем имени. Если была введена одна звезда, будут возвращены все файлы в каталоге. Если длина e больше 0, но не является единственной *, то точка e будет добавлена ​​к e, если e не содержала точку в нулевой позиции.

Для возвращаемого значения. Если возвращается карта нулевой длины, то ничего не найдено, но каталог открыт. Если индекс 999 доступен из возвращаемого значения, но размер карты равен только 1, то это означало, что возникла проблема с открытием пути к каталогу.

Обратите внимание, что для эффективности эту функцию можно разделить на 3 меньшие функции. Кроме того, вы можете создать функцию вызывающей стороны, которая будет определять, какую функцию она будет вызывать, основываясь на вводе. Почему это более эффективно? Сказано, что если вы собираетесь захватить все, что является файлом, то при использовании этого метода подфункция, созданная для захвата всех файлов, просто захватит все файлы и не будет нуждаться в оценке каких-либо других ненужных условий при каждом обнаружении файла. 

Это также относится к случаям, когда вы получаете файлы, которые не имеют расширения. Специальная встроенная функция для этой цели будет оценивать погоду только в том случае, если найденный объект является файлом, а затем, будет ли в имени файла точка.

Экономия может быть невелика, если вы только читаете каталоги с небольшим количеством файлов. Но если вы читаете большое количество каталогов или если каталог содержит пару сотен тысяч файлов, это может быть огромной экономией.

#include <stdio.h>
#include <sys/stat.h>
#include <iostream>
#include <dirent.h>
#include <map>

std::map<int, std::string> getFile(std::string p, std::string e = "", unsigned char s = '/'){
    if ( p.size() > 0 ){
        if (p.back() != s) p += s;
    }
    if ( e.size() > 0 ){
        if ( e.at(0) != '.' && !(e.size() == 1 && e.at(0) == '*') ) e = "." + e;
    }

    DIR *dir;
    struct dirent *ent;
    struct stat sb;
    std::map<int, std::string> r = {{999, "FAILED"}};
    std::string temp;
    int f = 0;
    bool fd;

    if ( (dir = opendir(p.c_str())) != NULL ){
        r.erase (999);
        while ((ent = readdir (dir)) != NULL){
            temp = ent->d_name;
            fd = temp.find(".") != std::string::npos? true : false;
            temp = p + temp;

            if (stat(temp.c_str(), &sb) == 0 && S_ISREG(sb.st_mode)){
                if ( e.size() == 1 && e.at(0) == '*' ){
                    r[f] = temp;
                    f++;
                } else {
                    if (e.size() == 0){
                        if ( fd == false ){
                            r[f] = temp;
                            f++;
                        }
                        continue;
                    }

                    if (e.size() > temp.size()) continue;

                    if ( temp.substr(temp.size() - e.size()) == e ){
                        r[f] = temp;
                        f++;
                    }
                }
            }
        }

        closedir(dir);
        return r;
    } else {
        return r;
    }
}

void printMap(auto &m){
    for (const auto &p : m) {
        std::cout << "m[" << p.first << "] = " << p.second << std::endl;
    }
}

int main(){
    std::map<int, std::string> k = getFile("./", "");
    printMap(k);
    return 0;
}
0
Kevin Ng