最快的方法是专门构建的程序,如下所示:
#include <stdio.h>
#include <dirent.h>
int main(int argc, char *argv[]) {
DIR *dir;
struct dirent *ent;
long count = 0;
dir = opendir(argv[1]);
while((ent = readdir(dir)))
++count;
closedir(dir);
printf("%s contains %ld files\n", argv[1], count);
return 0;
}
在不考虑缓存的情况下进行的测试中,我一遍又一遍地针对同一个目录运行了大约 50 次,以避免基于缓存的数据倾斜,我得到了大致以下性能数据(在实时时钟时间):
ls -1 | wc - 0:01.67
ls -f1 | wc - 0:00.14
find | wc - 0:00.22
dircnt | wc - 0:00.04
最后一个,dircnt,是从上述源码编译的程序。
编辑 2016-09-26
由于大众的需求,我把这个程序重写为递归的,所以它会放到子目录中,继续分别统计文件和目录。
由于很明显有些人想知道如何来完成所有这些工作,因此我在代码中添加了很多 cmets 来尝试让所发生的事情一目了然。我编写了这个并在 64 位 Linux 上对其进行了测试,但它应该适用于任何符合 POSIX 的系统,包括 Microsoft Windows。欢迎提交错误报告;如果您无法让它在您的 AIX 或 OS/400 或其他任何设备上运行,我很乐意更新它。
如您所见,它比原来的要复杂得多,而且必然如此:至少必须存在一个函数才能递归调用,除非您希望代码变得非常复杂(例如管理子目录堆栈并在单个循环中处理)。由于我们必须检查文件类型,不同操作系统、标准库等之间的差异会发挥作用,所以我编写了一个程序,试图在任何可以编译的系统上使用。
几乎没有错误检查,count 函数本身并不真正报告错误。唯一可能真正失败的调用是opendir 和stat(如果你不走运并且有一个dirent 已经包含文件类型的系统)。我对检查子目录路径名的总长度并不偏执,但理论上,系统不应允许任何长于PATH_MAX 的路径名。如果有问题,我可以解决这个问题,但只是需要向学习编写 C 的人解释更多代码。这个程序旨在作为如何递归地深入子目录的示例。
#include <stdio.h>
#include <dirent.h>
#include <string.h>
#include <stdlib.h>
#include <limits.h>
#include <sys/stat.h>
#if defined(WIN32) || defined(_WIN32)
#define PATH_SEPARATOR '\\'
#else
#define PATH_SEPARATOR '/'
#endif
/* A custom structure to hold separate file and directory counts */
struct filecount {
long dirs;
long files;
};
/*
* counts the number of files and directories in the specified directory.
*
* path - relative pathname of a directory whose files should be counted
* counts - pointer to struct containing file/dir counts
*/
void count(char *path, struct filecount *counts) {
DIR *dir; /* dir structure we are reading */
struct dirent *ent; /* directory entry currently being processed */
char subpath[PATH_MAX]; /* buffer for building complete subdir and file names */
/* Some systems don't have dirent.d_type field; we'll have to use stat() instead */
#if !defined ( _DIRENT_HAVE_D_TYPE )
struct stat statbuf; /* buffer for stat() info */
#endif
/* fprintf(stderr, "Opening dir %s\n", path); */
dir = opendir(path);
/* opendir failed... file likely doesn't exist or isn't a directory */
if(NULL == dir) {
perror(path);
return;
}
while((ent = readdir(dir))) {
if (strlen(path) + 1 + strlen(ent->d_name) > PATH_MAX) {
fprintf(stdout, "path too long (%ld) %s%c%s", (strlen(path) + 1 + strlen(ent->d_name)), path, PATH_SEPARATOR, ent->d_name);
return;
}
/* Use dirent.d_type if present, otherwise use stat() */
#if defined ( _DIRENT_HAVE_D_TYPE )
/* fprintf(stderr, "Using dirent.d_type\n"); */
if(DT_DIR == ent->d_type) {
#else
/* fprintf(stderr, "Don't have dirent.d_type, falling back to using stat()\n"); */
sprintf(subpath, "%s%c%s", path, PATH_SEPARATOR, ent->d_name);
if(lstat(subpath, &statbuf)) {
perror(subpath);
return;
}
if(S_ISDIR(statbuf.st_mode)) {
#endif
/* Skip "." and ".." directory entries... they are not "real" directories */
if(0 == strcmp("..", ent->d_name) || 0 == strcmp(".", ent->d_name)) {
/* fprintf(stderr, "This is %s, skipping\n", ent->d_name); */
} else {
sprintf(subpath, "%s%c%s", path, PATH_SEPARATOR, ent->d_name);
counts->dirs++;
count(subpath, counts);
}
} else {
counts->files++;
}
}
/* fprintf(stderr, "Closing dir %s\n", path); */
closedir(dir);
}
int main(int argc, char *argv[]) {
struct filecount counts;
counts.files = 0;
counts.dirs = 0;
count(argv[1], &counts);
/* If we found nothing, this is probably an error which has already been printed */
if(0 < counts.files || 0 < counts.dirs) {
printf("%s contains %ld files and %ld directories\n", argv[1], counts.files, counts.dirs);
}
return 0;
}
编辑 2017-01-17
我已经合并了@FlyingCodeMonkey 建议的两个更改:
- 使用
lstat 而不是stat。如果您正在扫描的目录中有符号链接目录,这将改变程序的行为。以前的行为是(链接的)子目录将其文件计数添加到总计数中;新行为是链接目录将计为单个文件,其内容将不计入。
- 如果文件的路径太长,则会发出错误消息并停止程序。
编辑 2017-06-29
运气好的话,这将是此答案的最后编辑:)
我已将此代码复制到 GitHub repository 中,以便更轻松地获取代码(而不是复制/粘贴,您只需 download the source),而且它使任何人都可以更轻松地提出修改建议通过从 GitHub 提交拉取请求。
源代码在 Apache 许可证 2.0 下可用。补丁*欢迎!