当大量文件(> 100,000)时,我试图找出在特定目录中查找文件数的最佳方法。
当文件太多时,执行ls | wc -l
将花费很长时间。我相信这是因为它正在返回所有文件的名称。我正在尝试占用尽可能少的磁盘IO。
我已经尝试了一些shell和Perl脚本,但无济于事。有任何想法吗?
当大量文件(> 100,000)时,我试图找出在特定目录中查找文件数的最佳方法。
当文件太多时,执行ls | wc -l
将花费很长时间。我相信这是因为它正在返回所有文件的名称。我正在尝试占用尽可能少的磁盘IO。
我已经尝试了一些shell和Perl脚本,但无济于事。有任何想法吗?
Answers:
默认情况下,ls
对名称进行排序,如果名称很多,则可能需要一段时间。在所有名称都被读取和排序之前,也不会有输出。使用该ls -f
选项可以关闭排序。
ls -f | wc -l
请注意,这也将让-a
,所以.
,..
开始和其他文件.
将被计算在内。
ls
。
stat()
调用相比ls
。因此,它find
不能stat()
更快地工作。
ls -f
也没有stat()
。但是当然两者都可以,ls
并且在使用某些选项(例如或)时find
调用。stat()
ls -l
find -mtime
ls -fR | wc -l
最快的方法是专用程序,如下所示:
#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
函数本身并没有真正报告错误。唯一可能真正失败的调用是opendir
和stat
(如果您不走运,并且拥有一个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建议的两个更改:
lstat
代替stat
。如果您正在扫描的目录中具有符号链接目录,则这将更改程序的行为。以前的行为是(链接的)子目录将其文件计数添加到总体计数中;新的行为是链接目录将被视为一个文件,并且其内容将不被计数。编辑2017-06-29
运气好的话,这将是这个答案的最后编辑:)
我已将此代码复制到GitHub存储库中,以使其变得更容易获得代码(而不是复制/粘贴,您只需下载源代码即可),并且它使任何人都可以通过提交请求来更轻松地提出修改建议-来自GitHub的请求。
该源可在Apache License 2.0下获得。补丁* 欢迎您!
gcc -o dircnt dircnt.c
就像这样使用./dircnt some_dir
您尝试找到了吗?例如:
find . -name "*.ext" | wc -l
find /usr/share | wc -l
(〜137,000个文件)比ls -R /usr/share | wc -l
每次(第一次)运行(〜160,000行,包括目录名称,目录总数和空行)快25%,比后续(缓存)运行的速度快至少两倍。
find
比ls
使用方法要快ls
。如果停止排序,ls
并find
具有类似的性能。
查找,对40 000个文件测试了ls和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 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来确保绕过alias选项,这可能会降低速度,而-f则避免文件排序。不带-f的ls比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的答案进行测试。
汤玛士
ls -l | wc -l
在带有1M文件的外部2.5英寸硬盘驱动器上的文件夹上运行,大约需要3分钟才能完成操作。第二次IIRC需要12秒。这也可能取决于您的文件系统。用Btrfs
。
$ time perl -e 'opendir D, "."; @files = readdir D; closedir D; print scalar(@files)."\n"' 1315029 real 0m0.580s user 0m0.302s sys 0m0.275s
您可以根据自己的要求更改输出,但这是我写的一个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,
ls -1 ${dir}
没有更多空间将无法正常工作。此外,也不能保证可以将返回的名称ls
传递给find
,因为ls
转义了无法打印的字符以供人类使用。(mkdir $'oddly\nnamed\ndirectory'
如果您想要一个特别有趣的测试用例)。请参阅为什么您不应该解析ls(1)的输出
令我惊讶的是,一个简单的发现与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
还会返回两个额外的单位,因为它也计算。和..)。
为了完整起见,仅添加此内容。正确的答案当然已经由其他人发布了,但是您还可以使用tree程序获得文件和目录的数量。
运行命令tree | tail -n 1
以获取最后一行,该行将显示类似“ 763目录,9290文件”的内容。这会递归地计算文件和文件夹,但隐藏文件除外,隐藏文件可以与标记一起添加-a
。作为参考,在我的计算机上花了4.8秒,树计算了我的整个主目录,该目录是24777个目录,238680个文件。find -type f | wc -l
花了5.3秒,延长了半秒,所以我认为树在速度方面非常有竞争力。
只要您没有任何子文件夹,tree都是计数文件的快速简便的方法。
另外,纯粹出于乐趣,您可以使用tree | grep '^├'
来仅显示当前目录中的文件/文件夹-这基本上是慢得多的版本ls
。
Brew install tail
为OS X
tail
应该已经安装在Mac OS X系统上。
我知道最快的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
它输出目录,文件等的数量。
由于我没有足够的声誉来评论答案,所以在这里写下来,但是我可以留下我自己的答案,这没有任何意义。无论如何...
关于Christopher Schultz的回答,我建议将stat更改为lstat,并可能添加边界检查以避免缓冲区溢出:
if (strlen(path) + strlen(PATH_SEPARATOR) + strlen(ent->d_name) > PATH_MAX) {
fprintf(stdout, "path too long (%ld) %s%c%s", (strlen(path) + strlen(PATH_SEPARATOR) + strlen(ent->d_name)), path, PATH_SEPARATOR, ent->d_name);
return;
}
建议使用lstat来避免遵循符号链接,如果目录包含指向父目录的符号链接,则可能导致循环。
lstat
是因为使用是一个很好的建议,您应该为此而报应。这个建议已合并到我上面和现在在GitHub上发布的代码中。
您可以尝试使用opendir()
和readdir()
in Perl
更快。有关这些功能的示例,请点击此处
对于非常大,非常嵌套的目录,此处的答案比此页面上的几乎所有其他内容都快:
https://serverfault.com/a/691372/84703
locate -r '.' | grep -c "^$PWD"
locate -c -r '/path'
像在abu_bua的解决方案中
当我尝试计算约1万个文件夹(每个文件约有10K个文件)的数据集中的文件时,我来到这里。许多方法的问题在于它们隐式统计100M文件,这需要很长时间。
我自由地使用christopher-schultz扩展了该方法,因此它支持通过args传递目录(他的递归方法也使用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/dirs/*
在10K文件夹中的100M文件中,以上操作很快完成(首次运行需要5分钟,对缓存的后续操作需要23秒)。
在不到一个小时的时间内完成的唯一另一种方法是ls,缓存大约1分钟:ls -f /your/dirs/* | wc -l
。尽管每个目录有几个换行符,但计数...
除了预期之外,find
一个小时内我的尝试都没有返回:-/
在linux上(问题标记为linux)最快的方法是使用直接系统调用。这是一个小程序,用于计算目录中的文件(仅,无目录)。您可以计算数百万个文件,它比“ ls -f”快2.5倍,比Christopher Schultz的答案快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上从源代码进行编译都是相当不错的建议)。如果速度是关键,那么如果它实际上可以提高速度,那么这是一个很好的解决方案-我没有分别对程序进行基准测试。
ls
花更多时间对文件名进行排序,使用-f
禁用排序将节省一些时间:
ls -f | wc -l
或者您可以使用find
:
find . -type f | wc -l
我意识到,当您拥有大量数据时,不使用内存处理比“添加”命令最快。所以我将结果保存到文件中,并进行了分析
ls -1 /path/to/dir > count.txt && cat count.txt | wc -l
您应该使用“ 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目录条目,这意味着如果您在同一目录中有很多文件(即500M目录条目),那么将花费很长时间来读取所有目录条目,尤其是在慢速磁盘上。对于包含大量文件的目录,您需要比依赖readdir()的工具更深入地研究。您将需要直接使用getdents()系统调用,而不是libc的辅助方法。
我们可以从这里找到使用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 insane number of files]
readdir()
实际上并不慢。在我相信有必要为此目的而放弃便携性之前,我需要有坚实的基础。
我更喜欢以下命令来跟踪目录中文件数量的变化。
watch -d -n 0.01 'ls | wc -l'
该命令将使窗口保持打开状态,以0.1秒的刷新速率跟踪目录中的文件数。
ls | wc -l
在0.01s内包含成千上万个文件的文件夹是否可以完成?ls
与其他解决方案相比,即使您的效率也非常低下。OP只是想获得计数,而不是坐在那里查看输出变化
watch
在评论后阅读了手册,并发现0.01s(而不是0.1s)是一个不切实际的数字,因为大多数PC屏幕的刷新率仅为60Hz,但这并不能以任何方式回答问题。OP询问“快速Linux计数大量文件”。在发布之前,您还没有阅读任何可用的答案