快速 Linux 文件计数为大量的文件

我试图找出当文件数量非常大(超过100,000个)时,在特定目录中查找文件数量的最佳方法。

当有这么多文件时,执行 ls | wc -l需要相当长的时间。我相信这是因为它正在返回所有文件的名称。我试图尽可能少地使用磁盘 I/O。

我曾经试验过一些 shell 和 Perl 脚本,但是没有用?

123705 次浏览

使用 找到,例如:

find . -name "*.ext" | wc -l

如果在 Perl中使用 opendir()readdir()更快,您可以尝试一下。

默认情况下,ls对名称进行排序,如果名称很多,这可能需要一段时间。在读取和排序所有名称之前,也不会有输出。使用 ls -f选项关闭排序。

ls -f | wc -l

注意 : 这也将启用 -a,因此 ...和以 .开头的其他文件将被计数。

令我惊讶的是,一个基本的发现非常类似于 ls-f

> time ls -f my_dir | wc -l
17626


real    0m0.015s
user    0m0.011s
sys     0m0.009s

VS

> time find my_dir -maxdepth 1 | wc -l
17625


real    0m0.014s
user    0m0.008s
sys     0m0.010s

当然,小数点后第三位的值每次执行时都会有一点偏移,所以它们基本上是相同的。但是请注意,find返回一个额外的单元,因为它计算实际目录本身(如前所述,ls -f返回两个额外的单元,因为它也计算。还有。.).

在40,000个文件中测试 find 是的Perl的速度是相同的(尽管我没有尝试清除缓存) :

[user@server logs]$ time find . | wc -l
42917


real    0m0.054s
user    0m0.018s
sys     0m0.040s


[user@server logs]$ time /bin/ls -f | wc -l
42918


real    0m0.059s
user    0m0.027s
sys     0m0.037s

与 Perl 的 OpendirRedir同时:

[user@server logs]$ time perl -e 'opendir D, "."; @files = readdir D; closedir D; print scalar(@files)."\n"'
42918


real    0m0.057s
user    0m0.024s
sys     0m0.033s

注意: 我使用/bin/ls-f 来确保绕过别名选项 也许吧稍微慢一点,绕过 -f来避免文件排序。 没有 -flsfind/perl慢两倍 除了如果 ls-f一起使用,似乎是相同的时间:

[user@server logs]$ time /bin/ls . | wc -l
42916


real    0m0.109s
user    0m0.070s
sys     0m0.044s

我还希望有一些脚本,直接要求文件系统没有所有不必要的信息。

这些测试是基于 Peter van der HeijdenGlenn JackmanMark4o的答案。

您可以根据需求更改输出,但是这里有一个我编写的 Bash 一行程序,用于递归地计算和报告一系列以数字命名的目录中的文件数量。

dir=/tmp/count_these/ ; for i in $(ls -1 ${dir} | sort -n) ; { echo "$i => $(find ${dir}${i} -type f | wc -l),"; }

这将递归地查找给定目录中的所有文件(不是目录) ,并以类哈希格式返回结果。对 find 命令的简单调整可以使您要查找的文件类型更加具体,等等。

结果是这样的:

1 => 38,
65 => 95052,
66 => 12823,
67 => 10572,
69 => 67275,
70 => 8105,
71 => 42052,
72 => 1184,

文件数量最多的前10个目录。

dir=/ ; for i in $(ls -1 ${dir} | sort -n) ; { echo "$(find ${dir}${i} \
-type f | wc -l) => $i,"; } | sort -nr | head -10

您可以通过 程序获得文件和目录的计数。

运行命令 tree | tail -n 1获取最后一行,它将显示类似于“763个目录,9290个文件”的内容。这将递归地计算文件和文件夹的数量,但不包括可以使用 -a标志添加的隐藏文件。作为参考,我的电脑花了4.8秒,树计算了我的整个主目录,24,777个目录,238,680个文件。find -type f | wc -l花了5.3秒,多了半秒,所以我认为 在速度上很有竞争力。

只要你没有任何子文件夹,是一个快速和简单的方法来计数文件。

另外,纯粹为了好玩,你可以使用 tree | grep '^├'来只显示工作目录中的文件/文件夹-这基本上是一个更慢的版本的 ls

最快的方法是建立一个特定的程序,像这样:

#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

由于受欢迎的需求,我已经重新编写了这个程序是递归的,所以它将下降到子目录,并继续计数文件和目录分开。

因为很明显,有些人想知道 怎么做是如何做到这一点的,所以我在代码中加入了很多注释,试图让代码显而易见。我编写了这个程序并在64位 Linux 上进行了测试,但是它 应该可以在任何符合 POSIX 的系统上工作,包括 Microsoft Windows。缺陷报告是受欢迎的; 如果您不能让它在您的 AIX 或 OS/400或其他操作系统上工作,我很乐意更新它。

正如您所看到的,它比原来的 很多更复杂,而且必须如此: 至少有一个函数必须存在才能被递归地调用,除非您希望代码变得非常复杂(例如,管理一个子目录堆栈并在一个循环中处理它)。由于我们必须检查文件类型,不同的操作系统、标准库等之间的差异就会发挥作用,所以我编写了一个程序,试图在任何系统上都可以使用。

错误检查非常少,而且 count函数本身并不真正报告错误。唯一可能真正失败的调用是 opendirstat(如果您运气不好,并且系统中的 dirent已经包含了文件类型)。我并不偏执于检查 subdir 路径名的总长度,但从理论上讲,系统不应该允许任何路径名长于 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 建议的两个变化:

  1. 使用 lstat代替 stat。如果在所扫描的目录中有符号链接目录,这将改变程序的行为。以前的行为是将(链接的)子目录的文件计数添加到总计数中; 新的行为是将链接的目录作为单个文件计数,而不计算其内容。
  2. 如果文件的路径太长,就会发出错误消息,程序就会停止。

编辑2017-06-29

如果幸运的话,这将是这个答案的 最后编辑:)

我已经把这段代码复制到 GitHub 存储库中,这样可以更容易地获得代码(而不是复制/粘贴,你只需要 下载源代码就可以了) ,此外,它还使得任何人都可以更容易地通过提交 GitHub 的拉请求来建议修改代码。

源代码可以在 Apache License 2.0下获得!


  • “补丁”是像我这样的老人所说的“拉请求”。

ls花费更多的时间对文件名进行排序。使用 -f禁用排序,这将节省一些时间:

ls -f | wc -l

或者你可以使用 find:

find . -type f | wc -l

对于非常大、非常嵌套的目录,这里的答案比这个页面上的几乎所有内容都要快:

Https://serverfault.com/a/691372/84703

locate -r '.' | grep -c "^$PWD"

我意识到,不在内存处理中使用,当您有大量的数据时,比“管道”命令更快。因此,我将结果保存到一个文件中,然后对其进行分析:

ls -1 /path/to/dir > count.txt && wc-l count.txt

我来这里是为了统计大约有10,000个文件夹,每个文件大约有10,000个文件的数据集中的文件数量。许多方法的问题在于它们隐含地统计了1亿个文件,这需要很长时间。

我擅自扩展了 克里斯托弗 · 舒尔茨的方法,使其支持通过参数传递目录(他的递归方法也使用 立刻)。

将以下内容输入 dircnt_args.c文件:

#include <stdio.h>
#include <dirent.h>


int main(int argc, char *argv[]) {
DIR *dir;
struct dirent *ent;
long count;
long countsum = 0;
int i;


for(i=1; i < argc; i++) {
dir = opendir(argv[i]);
count = 0;
while((ent = readdir(dir)))
++count;


closedir(dir);


printf("%s contains %ld files\n", argv[i], count);
countsum += count;
}
printf("sum: %ld\n", countsum);


return 0;
}

gcc -o dircnt_args dircnt_args.c之后,你可以这样调用它:

dircnt_args /your/directory/*

对于10,000个文件夹中的1亿个文件,上述操作完成得相当快(第一次运行大约需要5分钟,缓存的后续操作大约需要23秒)。

唯一在不到一个小时内完成的方法是 ls,缓存时间约为1分钟: ls -f /your/directory/* | wc -l。不过,每个目录的计数都偏离了几行... ..。

与预期不同的是,我使用 find的所有尝试在一个小时内都没有返回:-/

Linux 上最快的方法(这个问题被标记为 Linux)是使用直接的系统调用。下面是一个计算目录中文件(只有,没有目录)的小程序。您可以计算数百万个文件,它比“ ls-f”快约2.5倍,比 克里斯托弗 · 舒尔茨的回答快约1.3 -1.5倍。

#define _GNU_SOURCE
#include <dirent.h>
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/syscall.h>


#define BUF_SIZE 4096


struct linux_dirent {
long d_ino;
off_t d_off;
unsigned short d_reclen;
char d_name[];
};


int countDir(char *dir) {


int fd, nread, bpos, numFiles = 0;
char d_type, buf[BUF_SIZE];
struct linux_dirent *dirEntry;


fd = open(dir, O_RDONLY | O_DIRECTORY);
if (fd == -1) {
puts("open directory error");
exit(3);
}
while (1) {
nread = syscall(SYS_getdents, fd, buf, BUF_SIZE);
if (nread == -1) {
puts("getdents error");
exit(1);
}
if (nread == 0) {
break;
}


for (bpos = 0; bpos < nread;) {
dirEntry = (struct linux_dirent *) (buf + bpos);
d_type = *(buf + bpos + dirEntry->d_reclen - 1);
if (d_type == DT_REG) {
// Increase counter
numFiles++;
}
bpos += dirEntry->d_reclen;
}
}
close(fd);


return numFiles;
}


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


if (argc != 2) {
puts("Pass directory as parameter");
return 2;
}
printf("Number of files in %s: %d\n", argv[1], countDir(argv[1]));
return 0;
}

PS: 它不是递归的,但是你可以修改它来实现它。

你应该用“ getdent”代替 ls/find

这里有一篇非常好的文章,描述了获得者方法。

Http://be-n.com/spw/you-can-list-a-million-files-in-a-directory-but-not-with-ls.html

摘录如下:

ls以及列出目录的几乎所有其他方法(包括 Python 的 Os.listdirfind .)都依赖于 libc readdir ()。然而,readdir ()一次只读取32K 个目录条目,这意味着如果在同一个目录中有很多文件(例如,5亿个目录条目) ,那么读取所有目录条目将花费很长的时间,特别是在一个慢磁盘上。对于包含大量文件的目录,您需要比依赖 readdir ()的工具挖掘得更深。您将需要直接使用 getdent ()系统调用,而不是使用来自 C 标准库的 helper 方法。

我们可以从 给你中找到 C 代码,使用 getdent ()来列出文件:

为了快速列出一个目录中的所有文件,需要进行两个修改。

首先,将缓冲区大小从 X 增加到5MB 左右。

#define BUF_SIZE 1024*1024*5

然后修改主循环,打印出目录中每个文件的信息,跳过 inode = = 0的条目。我是这样做的

if (dp->d_ino != 0) printf(...);

在我的例子中,我实际上只关心目录中的文件名,所以我还重写了 printf ()语句,只打印文件名。

if(d->d_ino) printf("%sn ", (char *) d->d_name);

编译它(它不需要任何外部库,所以做起来非常简单)

gcc listdir.c -o listdir

快跑

./listdir [directory with an insane number of files]

快速 Linux 文件计数

据我所知,最快的 Linux 文件计数是

locate -c -r '/home'

没有需要调用 Grep!但是正如前面提到的,您应该有一个新的数据库(通过 cron 作业每天更新,或者通过 sudo updatedb手册更新)。

来自 找到人了

-c, --count
Instead  of  writing  file  names on standard output, write the number of matching
entries only.

附加 ,您应该知道它还将目录计为文件!


顺便说一句: 如果你想要你的文件和目录在你的系统类型的概览

locate -S

它输出目录、文件等的数量。

我喜欢使用下面的命令来跟踪目录中文件数量的变化。

watch -d -n 0.01 'ls | wc -l'

该命令将打开一个窗口,以跟踪目录中的文件数,刷新率为0.1秒。