大量文件的快速 Linux 文件计数
Posted
技术标签:
【中文标题】大量文件的快速 Linux 文件计数【英文标题】:Fast Linux file count for a large number of files 【发布时间】:2010-11-28 11:04:31 【问题描述】:当存在大量文件(超过 100,000 个)时,我正在尝试找出在特定目录中查找文件数量的最佳方法。
当有那么多文件时,执行ls | wc -l
需要很长时间才能执行。我相信这是因为它返回了所有文件的名称。我正在尝试尽可能少地占用磁盘 I/O。
我尝试了一些 shell 和 Perl 脚本,但无济于事。我该怎么做?
【问题讨论】:
确保您的“ls”是 /usr/bin/ls 而不是更高级的别名。 类似的问题在这里有有趣的答案:serverfault.com/questions/205071/… 值得指出的是,针对这个问题提出的大多数(如果不是全部)解决方案都不是特定于 Linux 的,而是对所有类似 *NIX 的系统都非常通用。也许删除“Linux”标签是合适的。 【参考方案1】:默认情况下ls
对名称进行排序,如果名称很多,这可能需要一段时间。在所有名称都被读取和排序之前,也不会有输出。使用ls -f
选项关闭排序。
ls -f | wc -l
注意:这也会启用-a
,所以.
、..
和其他以.
开头的文件都会被计算在内。
【讨论】:
+1 我以为我知道关于ls
的一切。
ZOMG。对 100K 行进行排序没什么 - 与 stat()
调用 ls
对每个文件所做的相比。 find
没有 stat()
因此它工作得更快。
ls -f
也不是stat()
。但当然ls
和find
在使用某些选项时都会调用stat()
,例如ls -l
或find -mtime
。
就上下文而言,这需要 1-2 分钟才能在一个小型 Slicehost 盒子上计算 250 万张 jpg。
如果要添加子目录到计数中,请执行ls -fR | wc -l
【参考方案2】:
最快的方法是专门构建的程序,如下所示:
#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 下可用。补丁*欢迎!
“补丁”就是像我这样的老人所说的“拉取请求”。
【讨论】:
太棒了!谢谢!对于那些不知道的人:您可以在终端中编译上述代码:gcc -o dircnt dircnt.c
并使用如下./dircnt some_dir
有没有简单的方法让这个递归?
@ck_ 当然,这很容易实现递归。您需要解决方案方面的帮助,还是希望我写下整个问题?
@ChristopherSchultz,您在上面发布的基准 - 有问题的目录有多大?
我真的很想在 Python 中使用它,所以我将它打包为 ffcount 包。感谢您提供代码@ChristopherSchultz!【参考方案3】:
使用find。例如:
find . -name "*.ext" | wc -l
【讨论】:
这将递归地在当前目录下查找文件。 如果他只想要当前目录,而不是递归的整个树,他可以添加 -maxdepth 1 选项来查找。 看来find
比ls
快的原因是因为您使用ls
的方式。如果停止排序,ls
和 find
的性能相似。
您可以通过仅打印一个字符来加快 find + wc:find . -printf x | wc -c
。否则,您将从整个路径创建字符串并将其传递给 wc(额外 I/O)。
你应该使用-printf
,正如@ives 显示的那样,所以当一些小丑在其中写入带有换行符的文件名时,计数是正确的。【参考方案4】:
find、ls 和 perl 测试 40,000 个文件具有相同的速度(虽然我没有尝试清除缓存):
[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 的 opendir 和 readdir:
[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
以避免文件排序。
ls
没有 -f
比 find
/perl
慢两倍
除非ls
与-f
一起使用,似乎是同一时间:
[user@server logs]$ time /bin/ls . | wc -l
42916
real 0m0.109s
user 0m0.070s
sys 0m0.044s
我也想要一些脚本来直接询问文件系统,而不需要所有不必要的信息。
测试基于Peter van der Heijden、glenn jackman和mark4o的答案。
【讨论】:
您绝对应该在测试之间清除缓存。我第一次在带有 1M 文件的外部 2.5" HDD 上的文件夹上运行ls -l | wc -l
时,操作完成大约需要 3 分钟。第二次需要 12 秒 IIRC。这也可能取决于您的文件系统也是。我用的是Btrfs
。
谢谢,perl sn-p 是我的解决方案。 $ time perl -e 'opendir D, "."; @files = readdir D; closedir D; print scalar(@files)."\n"' 1315029 real 0m0.580s user 0m0.302s sys 0m0.275s
您可以通过仅打印一个字符来加快 find + wc:find . -printf x | wc -c
。否则,您将从整个路径创建字符串并将其传递给 wc(额外 I/O)。【参考方案5】:
令我惊讶的是,一个简单的发现与 ls -f 非常相似
> time ls -f my_dir | wc -l
17626
real 0m0.015s
user 0m0.011s
sys 0m0.009s
对
> time find my_dir -maxdepth 1 | wc -l
17625
real 0m0.014s
user 0m0.008s
sys 0m0.010s
当然,每次执行这些操作时,小数点后第三位的值都会移动一点,因此它们基本上是相同的。但请注意,find
返回一个额外的单元,因为它计算实际目录本身(并且,如前所述,ls -f
返回两个额外的单元,因为它也计算 . 和 ..)。
【讨论】:
【参考方案6】:快速的 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
输出目录、文件等的数量
【讨论】:
请注意,您必须确保数据库是最新的 LOL 如果您已经拥有数据库中的所有计数,那么您当然可以快速计数。 :) 这对于近似值和估计值是合理的,但不适用于验证数据迁移等任务。【参考方案7】:您可以根据您的要求更改输出,但这是我编写的一个 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,
【讨论】:
我发现这个例子有点混乱。我想知道为什么左边有数字,而不是目录名。不过谢谢你,我最终用它做了一些小的调整。 (计算目录并删除基本文件夹名称。for i in $(ls -1 . | sort -n) ; echo "$i => $(find $i | wc -l)"; 左边的数字是我的示例数据中的目录名称。抱歉让您感到困惑。ls -1 $dir
没有更多空格将无法正常工作。此外,不能保证ls
返回的名称可以传递给find
,因为ls
会转义不可打印的字符以供人类使用。 (mkdir $'oddly\nnamed\ndirectory'
如果你想要一个特别有趣的测试用例)。见Why you shouldn't parse the output of ls(1)【参考方案8】:
ls
花费更多时间对文件名进行排序。使用-f
禁用排序,这样会节省一些时间:
ls -f | wc -l
或者你可以使用find
:
find . -type f | wc -l
【讨论】:
【参考方案9】:您可以使用tree 程序获取文件和目录的数量。
运行命令tree | tail -n 1
以获取最后一行,这将显示类似“763 个目录,9290 个文件”的内容。这会递归计算文件和文件夹,不包括隐藏文件,可以使用标志-a
添加。作为参考,在我的计算机上,tree 花了 4.8 秒来计算我的整个主目录,即 24,777 个目录,238,680 个文件。 find -type f | wc -l
花了 5.3 秒,多半秒,所以我认为 tree 在速度方面很有竞争力。
只要您没有任何子文件夹,tree 是一种快速简便的文件计数方法。
另外,纯粹为了好玩,您可以使用tree | grep '^├'
仅显示当前目录中的文件/文件夹 - 这基本上是ls
的一个慢得多的版本。
【讨论】:
Brew install tail
用于 OS X。
@TheUnfunCat tail
应该已经安装在您的 Mac OS X 系统上。【参考方案10】:
您应该使用“getdents”代替 ls/find
这是一篇非常好的文章,描述了 getdents 方法。
http://be-n.com/spw/you-can-list-a-million-files-in-a-directory-but-not-with-ls.html
摘录如下:
ls
以及几乎所有其他列出目录的方法(包括 Python 的 os.listdir 和 find .
)都依赖于 libc readdir()。但是,readdir() 一次只能读取 32K 的目录条目,这意味着如果您在同一个目录中有很多文件(例如,5 亿个目录条目),那么读取所有目录将花费非常长的时间。目录条目,尤其是在慢速磁盘上。对于包含大量文件的目录,您需要比依赖 readdir() 的工具更深入地挖掘。您需要直接使用 getdents() 系统调用,而不是来自 C standard library 的辅助方法。
我们可以从here找到使用getdents()列出文件的C代码:
为了快速列出目录中的所有文件,您需要进行两项修改。
首先,将缓冲区大小从 X 增加到 5 兆字节。
#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 会进行预读,因此readdir()
实际上并不慢。在我相信为了性能提升而放弃便携性之前,我需要可靠的数据。
你能添加一些基准,比较这两种方法吗?包括。在什么条件下,例如文件数量、冷/热文件系统缓存、硬件、磁盘类型(HDD 与 SSD)、文件系统类型(例如 ext4 或 NTFS)、磁盘碎片状态、计算机系统和操作系统(例如 Ubuntu 16.04),带有版本信息))?您可以edit your answer(但没有“编辑:”、“更新:”或类似名称)。
getdents() 的作用域是什么?仅适用于 Linux?【参考方案11】:
对于非常大、非常嵌套的目录,这里的答案比此页面上的几乎所有其他内容都要快:
https://serverfault.com/a/691372/84703
locate -r '.' | grep -c "^$PWD"
【讨论】:
不错。由于您已经拥有所有文件的最新数据库,因此无需再次使用它。但不幸的是,您必须确保该方法的 updatedb 命令已经运行并完成。 你不需要grep。使用locate -c -r '/path'
就像在 abu_bua's solution 中一样【参考方案12】:
如果在Perl
中使用opendir()
和readdir()
更快,您可以尝试。有关这些功能的示例,请查看 here。
【讨论】:
用法:perl -e 'opendir D, "."; @files = readdir D;关闭 D;打印标量(@files)'【参考方案13】:我是在尝试计算包含大约 10,000 个文件夹(每个文件夹大约 10,000 个文件)的数据集中的文件时来到这里的。许多方法的问题在于它们隐含地统计了 1 亿个文件,这需要很长时间。
我冒昧地扩展了the approach by Christopher Schultz,因此它支持通过参数传递目录(他的递归方法也使用 stat)。
将以下内容放入文件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
的尝试在一个小时内都没有返回:-/
【讨论】:
对于不是 C 程序员的人,你能解释一下为什么这会更快,以及它如何能够在不做同样事情的情况下得到相同的答案? 您不必是 C 程序员,只需了解统计文件的含义以及目录的表示方式:目录本质上是文件名和 inode 的列表。如果您统计一个文件,您可以访问驱动器上某处的 inode,例如获取文件大小、权限等信息。如果您只对每个目录的计数感兴趣,则无需访问 inode 信息,这可能会为您节省大量时间。 Oracle linux 上的段错误,gcc 版本 4.8.5 20150623 (Red Hat 4.8.5-28.0.1) (GCC)...相对路径和远程 fs 似乎是原因 Re “不过,每个目录的计数值相差几个换行符”:这可以通过将-f
与-A
(大写'a')结合使用来解决:ls -f -A
。选项-f
启用-a
(小写'a'),但它可以被-A
覆盖。这是用ls
8.30 版测试的。【参考方案14】:
Linux 上最快的方法(问题标记为 Linux)是使用直接系统调用。这是一个计算目录中文件(仅,无目录)的小程序。您可以计算数百万个文件,它比“ls -f”快约 2.5 倍,比 Christopher Schultz's answer 快约 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:它不是递归的,但你可以修改它来实现。
【讨论】:
我不确定我是否同意这样做更快。我没有追溯编译器对opendir
/readdir
所做的所有事情,但我怀疑它最终归结为几乎相同的代码。以这种方式进行系统调用也不可移植,并且由于 Linux ABI 不稳定,不能保证在一个系统上编译的程序在另一个系统上正常工作(尽管在任何 *NIX 系统 IMO 上从源代码编译任何东西都是相当好的建议)。如果速度是关键,那么如果它确实提高了速度,这是一个很好的解决方案——我没有单独对程序进行基准测试。【参考方案15】:
我意识到,当您拥有大量数据时,不使用内存处理比“管道”命令要快。所以我将结果保存到一个文件中,然后进行分析:
ls -1 /path/to/dir > count.txt && wc-l count.txt
【讨论】:
这不是最快的解决方案,因为硬盘非常慢。还有其他更有效的方法在你之前几年就发布了 您能否将两种方式(管道和中间文件)的实际测量值添加到您的答案中(包括在什么条件下,例如文件数、硬件、磁盘类型(HDD 与 SSD)、文件系统类型(例如ext4 或NTFS)、磁盘碎片状态、计算机系统和操作系统(例如Ubuntu 16.04),带有版本信息))?您可以edit your answer(但没有“编辑:”、“更新:”或类似内容)。【参考方案16】:文件数最多的前 10 个目录。
dir=/ ; for i in $(ls -1 $dir | sort -n) ; echo "$(find $dir$i \
-type f | wc -l) => $i,"; | sort -nr | head -10
【讨论】:
这肯定与written by mightybs 的答案(具有相同的错误)惊人地相似。如果您要扩展或修改其他人编写的代码,则将其归功于他们是合适的。充分了解您在答案中使用的代码以识别和修复其错误甚至更合适。【参考方案17】:我更喜欢以下命令来跟踪目录中文件数量的变化。
watch -d -n 0.01 'ls | wc -l'
该命令将保持一个窗口打开,以 0.1 秒的刷新率跟踪目录中的文件数。
【讨论】:
您确定ls | wc -l
将在 0.01 秒内完成包含数千或数百万个文件的文件夹吗?与其他解决方案相比,即使您的 ls
效率也非常低。而 OP 只想得到计数,而不是坐在那里看着输出变化
好吧。好吧。我找到了一个适合我的优雅解决方案。我想分享相同的,因此做到了。我不知道linux中的'ls'命令效率很低。你用什么代替那个?而0.01s是刷新率。不是时候。如果您没有使用过手表,请参考手册页。
好吧,在那条评论之后我确实阅读了watch
手册,发现 0.01s(不是 0.1s)是一个不切实际的数字,因为大多数 PC 屏幕的刷新率只有 60Hz,而这并不是t 以任何方式回答问题。 OP 询问“大量文件的快速 Linux 文件计数”。您在发布之前也没有阅读任何可用的答案
我确实阅读了答案。但是我发布的是一种跟踪目录中文件数量变化的方法。例如:在将文件从一个位置复制到另一个位置时,文件的数量会不断变化。使用我发布的方法可以跟踪这一点。我同意我发的帖子没有修改或改进任何以前的帖子。
这个问题特别想要比ls | wc -l
快的东西,这显然不是。以上是关于大量文件的快速 Linux 文件计数的主要内容,如果未能解决你的问题,请参考以下文章