我试图找出当文件数量非常大(超过100,000个)时,在特定目录中查找文件数量的最佳方法。
当有这么多文件时,执行 ls | wc -l需要相当长的时间。我相信这是因为它正在返回所有文件的名称。我试图尽可能少地使用磁盘 I/O。
ls | wc -l
我曾经试验过一些 shell 和 Perl 脚本,但是没有用?
使用 找到,例如:
find . -name "*.ext" | wc -l
如果在 Perl中使用 opendir()和 readdir()更快,您可以尝试一下。
Perl
opendir()
readdir()
默认情况下,ls对名称进行排序,如果名称很多,这可能需要一段时间。在读取和排序所有名称之前,也不会有输出。使用 ls -f选项关闭排序。
ls
ls -f
ls -f | wc -l
注意 : 这也将启用 -a,因此 .、 ..和以 .开头的其他文件将被计数。
-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返回两个额外的单元,因为它也计算。还有。.).
find
在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 的 Opendir和 Redir同时:
[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来避免文件排序。 没有 -f的 ls比 find/perl慢两倍 除了如果 ls与 -f一起使用,似乎是相同的时间:
-f
perl
[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的答案。
您可以根据需求更改输出,但是这里有一个我编写的 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 | tail -n 1
find -type f | wc -l
只要你没有任何子文件夹,树是一个快速和简单的方法来计数文件。
另外,纯粹为了好玩,你可以使用 tree | grep '^├'来只显示工作目录中的文件/文件夹-这基本上是一个更慢的版本的 ls。
tree | grep '^├'
最快的方法是建立一个特定的程序,像这样:
#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是根据上面的源代码编译的程序。
dircnt
编辑2016-09-26
由于受欢迎的需求,我已经重新编写了这个程序是递归的,所以它将下降到子目录,并继续计数文件和目录分开。
因为很明显,有些人想知道 怎么做是如何做到这一点的,所以我在代码中加入了很多注释,试图让代码显而易见。我编写了这个程序并在64位 Linux 上进行了测试,但是它 应该可以在任何符合 POSIX 的系统上工作,包括 Microsoft Windows。缺陷报告是受欢迎的; 如果您不能让它在您的 AIX 或 OS/400或其他操作系统上工作,我很乐意更新它。
正如您所看到的,它比原来的 很多更复杂,而且必须如此: 至少有一个函数必须存在才能被递归地调用,除非您希望代码变得非常复杂(例如,管理一个子目录堆栈并在一个循环中处理它)。由于我们必须检查文件类型,不同的操作系统、标准库等之间的差异就会发挥作用,所以我编写了一个程序,试图在任何系统上都可以使用。
错误检查非常少,而且 count函数本身并不真正报告错误。唯一可能真正失败的调用是 opendir和 stat(如果您运气不好,并且系统中的 dirent已经包含了文件类型)。我并不偏执于检查 subdir 路径名的总长度,但从理论上讲,系统不应该允许任何路径名长于 PATH_MAX。如果有问题,我可以解决,但是需要向学习编写 C 的人解释更多的代码。这个程序旨在作为一个例子,说明如何递归地进入子目录。
count
opendir
stat
dirent
PATH_MAX
#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
编辑2017-06-29
如果幸运的话,这将是这个答案的 最后编辑:)
我已经把这段代码复制到 GitHub 存储库中,这样可以更容易地获得代码(而不是复制/粘贴,你只需要 下载源代码就可以了) ,此外,它还使得任何人都可以更容易地通过提交 GitHub 的拉请求来建议修改代码。
源代码可以在 Apache License 2.0下获得!
ls花费更多的时间对文件名进行排序。使用 -f禁用排序,这将节省一些时间:
或者你可以使用 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文件:
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之后,你可以这样调用它:
gcc -o dircnt_args dircnt_args.c
dircnt_args /your/directory/*
对于10,000个文件夹中的1亿个文件,上述操作完成得相当快(第一次运行大约需要5分钟,缓存的后续操作大约需要23秒)。
唯一在不到一个小时内完成的方法是 ls,缓存时间约为1分钟: ls -f /your/directory/* | wc -l。不过,每个目录的计数都偏离了几行... ..。
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.listdir和 find .)都依赖于 libc readdir ()。然而,readdir ()一次只读取32K 个目录条目,这意味着如果在同一个目录中有很多文件(例如,5亿个目录条目) ,那么读取所有目录条目将花费很长的时间,特别是在一个慢磁盘上。对于包含大量文件的目录,您需要比依赖 readdir ()的工具挖掘得更深。您将需要直接使用 getdent ()系统调用,而不是使用来自 C 标准库的 helper 方法。
find .
我们可以从 给你中找到 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 文件计数是
locate -c -r '/home'
有 没有需要调用 Grep!但是正如前面提到的,您应该有一个新的数据库(通过 cron 作业每天更新,或者通过 sudo updatedb手册更新)。
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秒。