在C++中递归列出文件夹并不能进入所有子目录

3

!!!已解决!!!

谢谢大家的帮助,现在所有东西都可以正常工作了。我按照@RSahu的建议修改了我的代码并使其正常工作。
感谢大家的支持,我一直被这个问题困扰着。
对于@Basile:我一定会去看看的,但是对于这段特定的代码,我不会使用它,因为它看起来太复杂了 :) 不过还是感谢你的建议。



原始问题

我正在尝试制作一个C++代码来列出给定目录及其子目录中的所有文件。

快速说明

Idea 是函数list_dirs(_dir, _files, _current_dir)将从顶级目录开始,并将文件放入向量_files中,当它找到一个目录时,它将在此目录上调用自身。_current_dir用于在子目录中添加到文件名之前,因为我需要知道路径结构(它应该生成sitemap.xml)。
list_dirs中,有一个对list_dir的调用,它只返回当前目录中的所有文件,没有区分文件和目录。

我的问题

现在代码所做的是列出原始目录中的所有文件,然后列出一个子目录中的所有文件,但跳过所有其他子目录。它会列出它们,但不会列出其中的文件。
更加神秘的是,它只列出这一个特定目录中的文件,而没有其他文件。我尝试在多个位置运行它,但它从未进入任何其他目录。

先感谢大家,并请注意我是C++的初学者,所以不要太苛刻了;)
LIST_DIR

int list_dir(const std::string& dir, std::vector<std::string>& files){
    DIR *dp;
    struct dirent *dirp;
    unsigned fileCount = 0;

    if ((dp = opendir(dir.c_str())) == NULL){
        std::cout << "Error opening dir." << std::endl;
    }

    while ((dirp = readdir(dp)) != NULL){
        files.push_back(std::string (dirp->d_name));
        fileCount++;
    }

    closedir(dp);
    return fileCount;
}

以及 LIST_DIRS

int list_dirs (const std::string& _dir, std::vector<std::string>& _files, std::string _current_dir){
    std::vector<std::string> __files_or_dirs;

    list_dir(_dir, __files_or_dirs);

    std::vector<std::string>::iterator it = __files_or_dirs.begin();
    struct stat sb;

    while (it != __files_or_dirs.end()){
        if (lstat((&*it)->c_str(), &sb) == 0 && S_ISDIR(sb.st_mode)){
            /* how to do this better? */
            if (*it == "." || *it == ".."){
                __files_or_dirs.erase(it);
                continue;
            }

            /* here it should go into sub-directory */
            list_dirs(_dir + *it, _files, _current_dir + *it);

            __files_or_dirs.erase(it);
        } else {
            if (_current_dir.empty()){
                _files.push_back(*it);
            } else {
                _files.push_back(_current_dir + "/" + *it);
            }
            ++it;
        }
    }
}
4个回答

2
主要问题出在这一行:
if (lstat((&*it)->c_str(), &sb) == 0 && S_ISDIR(sb.st_mode)){

在调用lstat函数时,您使用了目录项的名称。当该函数处理子目录时,该条目名称并不代表有效路径。您需要使用类似以下的内容:

std::string entry = *it;
std::string full_path = _dir + "/" + entry;
if (lstat(full_path.c_str(), &sb) == 0 && S_ISDIR(sb.st_mode)){

改进建议

更新 list_dir 函数,使其在输出时不包含 "."".."。从一开始就排除这些文件对我来说是有意义的。

int list_dir(const std::string& dir, std::vector<std::string>& files){
   DIR *dp;
   struct dirent *dirp;
   unsigned fileCount = 0;

   if ((dp = opendir(dir.c_str())) == NULL){
      std::cout << "Error opening dir." << std::endl;
   }

   while ((dirp = readdir(dp)) != NULL){
      std::string entry = dirp->d_name;
      if ( entry == "." or entry == ".." )
      {
         continue;
      }

      files.push_back(entry);
      fileCount++;
   }

   closedir(dp);
   return fileCount;
}

list_dirs中,没有必要从_files_or_dirs中删除项目。可以使用for循环简化代码,并删除从_files_or_dirs中删除项目的调用。
我不清楚_current_dir的目的是什么,也许它可以被删除。
这是函数的更新版本。只有在递归调用的参数值构造中才使用_current_dir
int list_dirs (const std::string& _dir,
               std::vector<std::string>& _files, std::string _current_dir){
   std::vector<std::string> __files_or_dirs;

   list_dir(_dir, __files_or_dirs);

   std::vector<std::string>::iterator it = __files_or_dirs.begin();
   struct stat sb;

   for (; it != __files_or_dirs.end() ; ++it){
      std::string entry = *it;
      std::string full_path = _dir + "/" + entry;

      if (lstat(full_path.c_str(), &sb) == 0 && S_ISDIR(sb.st_mode)){
         /* how to do this better? */

         /* here it should go into sub-directory */
         list_dirs(full_path, _files, _current_dir + "/" + entry);

      } else {
         _files.push_back(full_path);
      }
   }
}

1

For this line:

   if (lstat((&*it)->c_str(), &sb) == 0 && S_ISDIR(sb.st_mode)){

请注意,readdir和因此list_dir仅返回文件名,而不是完整的文件路径。因此,在这一点上,(&*it)->c_str()只有一个文件名(例如“input.txt”),而不是完整路径,因此当您在子目录中的文件上调用lstat时,系统找不到它。
要解决此问题,您需要在调用lstat之前添加文件路径。类似以下内容:
   string fullFileName;
   if (dir.empty()){
       fullFileName = *it;
   } else {
       fullFileName = dir + "/" + *it;
   }

   if (lstat(fullFileName.c_str(), &sb) == 0 && S_ISDIR(sb.st_mode)){

根据它们实际的用途(我无法理解您的解释),您可能需要使用_currentDir而不是dir


1

我不确定你的代码中所有问题,但我可以告诉你这一行和另一行类似的代码会导致问题:

__files_or_dirs.erase(it);

当您调用erase时,会使得迭代器和指向被删除元素之后的所有引用失效,包括end()迭代器(请参阅erase reference)。您正在调用erase,但未存储返回的迭代器,并且在此调用之后再次查看它,这是不好的行为。您应该至少更改以下行,以便捕获返回的迭代器,该迭代器应该指向被删除元素之后的元素(如果是最后一个元素,则指向end())。
it = __files_or_dirs.erase(it);

从您发布的代码中看来,_dir_current_dir之间存在冗余。您没有修改它们中的任何一个。您将它们作为相同的值传递,并且它们在函数执行期间保持相同的值。除非这是简化的代码,您正在做其他事情,否则我建议您删除_current_dir并仅使用_dir。您可以在构建文件名的while循环中将该行替换为_dir,这样您就简化了代码,这总是一件好事。

哦,谢谢你指出来,我已经相应地修改了我的代码,并将在以后记住这一点。 - lsrom
我无法完全按照您在编辑中提出的建议进行操作,因为我需要在输出向量中列出子目录,而不是原始目录。这就是为什么我需要“_current_dir”参数的原因。如果我仅使用“_dir”参数,那么原始目录中的所有文件都将具有original_dir/filename格式,而我只想为子目录采用此格式。 - lsrom
@Lukᚊrom - 我明白你的意思,我没有考虑到你对函数的初始调用,它看起来像 list_dirs("somedir", files, ""); 所以 _current_dir 不等于 dir。我会划掉我的编辑(抱歉,我在一个很晚的时间,没有完全考虑清楚)。 - pstrjds

1
在Linux上更简单的方法是使用nftw(3)函数。它递归扫描文件树,您可以给它一些处理程序函数。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接