C++中的递归文件夹扫描

14
我想扫描目录树并列出每个目录内的所有文件和文件夹。我创建了一个程序,从网络摄像头下载图像并保存到本地。该程序基于图片下载时间创建文件树。现在我想扫描这些文件夹并将图片上传到Web服务器,但我不确定如何扫描目录以查找图片。 如果有人能发布一些示例代码,那将非常有帮助。
编辑:我正在嵌入式Linux系统上运行此程序,并且不想使用boost。
6个回答

32

参见man ftw以获取简单的“文件树遍历”示例。我还在此示例中使用了fnmatch

#include <ftw.h>
#include <fnmatch.h>

static const char *filters[] = {
    "*.jpg", "*.jpeg", "*.gif", "*.png"
};

static int callback(const char *fpath, const struct stat *sb, int typeflag) {
    /* if it's a file */
    if (typeflag == FTW_F) {
        int i;
        /* for each filter, */
        for (i = 0; i < sizeof(filters) / sizeof(filters[0]); i++) {
            /* if the filename matches the filter, */
            if (fnmatch(filters[i], fpath, FNM_CASEFOLD) == 0) {
                /* do something */
                printf("found image: %s\n", fpath);
                break;
            }
        }
    }

    /* tell ftw to continue */
    return 0;
}

int main() {
    ftw(".", callback, 16);
}

(甚至没有经过编译测试,但你能明白大概的意思。)

这比自己处理DIRENT和递归遍历要简单得多。


如果想要更好地控制遍历,也可以使用fts。在本例中,跳过了以点“.”开头的文件和目录(即隐藏文件),除非它们被显式地传递给程序作为起始点。

#include <fts.h>
#include <string.h>

int main(int argc, char **argv) {
    char *dot[] = {".", 0};
    char **paths = argc > 1 ? argv + 1 : dot;

    FTS *tree = fts_open(paths, FTS_NOCHDIR, 0);
    if (!tree) {
        perror("fts_open");
        return 1;
    }

    FTSENT *node;
    while ((node = fts_read(tree))) {
        if (node->fts_level > 0 && node->fts_name[0] == '.')
            fts_set(tree, node, FTS_SKIP);
        else if (node->fts_info & FTS_F) {
            printf("got file named %s at depth %d, "
                "accessible via %s from the current directory "
                "or via %s from the original starting directory\n",
                node->fts_name, node->fts_level,
                node->fts_accpath, node->fts_path);
            /* if fts_open is not given FTS_NOCHDIR,
             * fts may change the program's current working directory */
        }
    }
    if (errno) {
        perror("fts_read");
        return 1;
    }

    if (fts_close(tree)) {
        perror("fts_close");
        return 1;
    }

    return 0;
}

再次说明,此代码既没有经过编译测试也没有经过运行测试,但我认为值得一提。


FTS示例非常好用。我只需要做的更改是将"ftsread"改为"fts_read",并且我必须将fts_read的结果转换为(FTSENT*)。我本以为在网络上找到这样的代码会更容易,但这绝对是我找到的最干净的示例。 谢谢! - Hortitude
1
fts_info的值不是单个位...选项FTS_D到FTS_W被定义为顺序值1到14。对于代码中的"& FTS_F"没有解释说明可能会让一些人感到困惑。有可能人们打算在这里获取FTS_F、FTS_INIT、FTS_NS、FTS_NSOK、FTS_SL、FTS_SLNONE和FTS_W...但是FTS_NS或FTS_NSOK可能有点奇怪。此外,我在网上看到一些代码使用"& FTS_D",几乎肯定不是他们想要的(获取FTS_ERR、FTS_DEFAULT)。 - darron

6
Boost.Filesystem允许你实现这一点。查看文档
编辑:
如果你使用Linux并且不想使用Boost,你将不得不使用Linux本地的C函数。 这个页面展示了如何做到这一点的许多例子。

2

我是老派的,不用ftw()!这段代码粗糙(已经很久没有写过纯C编程了),有很多硬编码的地方,而且我可能弄错了strnc*()函数的长度计算,但你能理解我的意思。顺便说一下,K&R里有一个类似的示例。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

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

void listdir(char* dirname, int lvl);

int main(int argc, char** argv)
{

  if (argc != 2) {
    fprintf(stderr, "Incorrect usage!\n");
    exit(-1);
  }
  listdir(argv[1], 0);


  return 0;
}

void listdir(char* dirname, int lvl)
{

  int i;
  DIR* d_fh;
  struct dirent* entry;
  char longest_name[4096];

  while( (d_fh = opendir(dirname)) == NULL) {
    fprintf(stderr, "Couldn't open directory: %s\n", dirname);
    exit(-1);
  }

  while((entry=readdir(d_fh)) != NULL) {

    /* Don't descend up the tree or include the current directory */
    if(strncmp(entry->d_name, "..", 2) != 0 &&
       strncmp(entry->d_name, ".", 1) != 0) {

      /* If it's a directory print it's name and recurse into it */
      if (entry->d_type == DT_DIR) {
        for(i=0; i < 2*lvl; i++) {
          printf(" ");
        }
        printf("%s (d)\n", entry->d_name);

        /* Prepend the current directory and recurse */
        strncpy(longest_name, dirname, 4095);
        strncat(longest_name, "/", 4095);
        strncat(longest_name, entry->d_name, 4095);
        listdir(longest_name, lvl+1);
      }
      else {

        /* Print some leading space depending on the directory level */
        for(i=0; i < 2*lvl; i++) {
          printf(" ");
        }
        printf("%s\n", entry->d_name);
      }
    }
  }

  closedir(d_fh);

  return;
}

strncmp(name, ".", 1) 将排除点文件。此外,最好跳过(并发出警告),而不是尝试在截断的名称下继续进行。最后,与其使用循环,不如使用这个巧妙的缩进打印技巧:static char spaces[] = " "; printf("%s", spaces[max(0, strlen(spaces) - count)]); - ephemient

1

你也可以使用 glob/globfree。


比起自己读取目录并进行匹配,这种方法更加简单,但仍然不支持递归。当然,使用GLOB_ONLYDIR可以避免完全处理DIRENTs,但它仍然不如ftw方便,而且纯名称遍历是有竞争条件的。 - ephemient

0

我认为如果您可以使用Qt/Embedded,则有QDir和QFileInfo类可帮助您,尽管这取决于您是否能够使用Qt。问题是您的系统提供哪个API。


0

您需要使用在dirent.h中声明的目录函数。这个wikipedia页面描述了它们并包含示例代码。对于您的应用程序,一旦您确定了一个目录,您将希望递归地再次调用处理函数以处理目录内容。


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