我正在尝试找出一种最佳的方法,在有大量文件(超过100,000个)的情况下,查找特定目录中的文件数量。
当存在如此多的文件时,执行 ls | wc -l
要花费相当长的时间。我认为这是因为它返回了所有文件的名称。 我正在尽可能少地使用磁盘I/O。
我已经尝试了一些Shell和Perl脚本,但都没有成功。 我该怎么做?
我正在尝试找出一种最佳的方法,在有大量文件(超过100,000个)的情况下,查找特定目录中的文件数量。
当存在如此多的文件时,执行 ls | wc -l
要花费相当长的时间。我认为这是因为它返回了所有文件的名称。 我正在尽可能少地使用磁盘I/O。
我已经尝试了一些Shell和Perl脚本,但都没有成功。 我该怎么做?
默认情况下,ls
会对名称进行排序,如果名称很多,这可能需要一段时间。此外,在读取和排序所有名称之前,不会有任何输出。使用ls -f
选项关闭排序。
ls -f | wc -l
注意: 这也将启用-a
,因此以.
开头的.
、..
和其他文件也会被计算在内。
ls
命令在每个文件上进行 stat()
调用的操作来说,对 10 万行的排序算不了什么。相比之下,find
命令不会进行 stat()
操作,因此速度更快。注意保持原意,使文意更通俗易懂。 - Dummy00001ls -f
不执行 stat()
。但是当特定选项被使用时,例如 ls -l
或 find -mtime
,ls
和 find
都会调用 stat()
。 - mark4ols -fR | wc -l
。 - Ryan Walls#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
的程序是从上述源代码编译出来的。#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 提交 pull-request 来更容易地建议修改。
源代码在Apache许可证2.0下可用。欢迎提交补丁*!
gcc -o dircnt dircnt.c
,然后像这样使用它:./dircnt some_dir
。 - aesede使用find命令。例如:
find . -name "*.ext" | wc -l
find /usr/share | wc -l
(约137,000个文件)比ls -R /usr/share | wc -l
(包括目录名、目录总计和空行的大约160,000行)第一次运行时快25%,当比较后续(缓存)运行时至少快两倍。 - Dennis Williamsonfind
比 ls
更快的原因是你使用 ls
的方式。如果停止排序,则 ls
和 find
的性能相似。 - Christopher Schultzfind + wc
命令,你可以只打印一个字符:find . -printf x | wc -c
。否则,你会从整个路径中创建字符串并将其传递给 wc(多余的输入/输出操作)。 - ivesfind、ls和perl在测试4万个文件时速度相同(尽管我没有尝试清除缓存):
[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
用于避免文件排序。没有 -f
的 ls
比 find
/perl
慢两倍,除非 ls
与 -f
一起使用,这时候时间似乎一样:
[user@server logs]$ time /bin/ls . | wc -l
42916
real 0m0.109s
user 0m0.070s
sys 0m0.044s
ls -l | wc -l
命令时,操作需要大约3分钟才能完成。第二次只需12秒左右,如我所记得的那样。另外,这也可能取决于您的文件系统。我使用的是 Btrfs。 - Behrangfind + wc
的速度:find . -printf x | wc -c
。否则,您将从整个路径创建字符串并将其传递给 wc(额外的 I/O)。 - ives令我惊讶的是,一个简单的find命令和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
返回两个额外的单位,因为它还计算 . 和 ..)。我知道的最快的Linux文件计数方法是:
locate -c -r '/home'
不需要调用grep!但是如前所述,您应该拥有一个新鲜的数据库(由cron作业每天更新,或通过sudo updatedb
手动更新)。
来自locate手册
-c, --count
Instead of writing file names on standard output, write the number of matching
entries only.
另外要注意的是,它也将目录视为文件!
顺带一提:如果您想查看系统中文件和目录的概述,请输入以下命令:
locate -S
它输出目录、文件等数量。
您可以根据您的要求更改输出,但这里是我编写的 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)的输出。 - Charles Duffyls
命令需要花费更多的时间来对文件名进行排序。使用 -f
参数可以禁用排序,这样可以节省一些时间:
ls -f | wc -l
或者您可以使用 find
:
find . -type f | wc -l
tree | tail -n 1
以获取最后一行,该行将显示类似于“763 directories, 9290 files”的内容。这个命令会递归地计算文件和文件夹的数量,但不包括隐藏文件,如果需要包括隐藏文件,可以添加标志-a
。参考资料显示,在我的电脑上,tree 计算整个 home 目录花费了4.8秒,其中包括24,777个目录和238,680个文件。而find -type f | wc -l
命令需要5.3秒,比 tree 命令慢了半秒钟,因此我认为 tree 在速度方面相当有竞争力。tree | grep '^├'
仅显示当前目录中的文件/文件夹 - 这基本上是ls
命令的缓慢版本。Brew install tail
for OS X. - The Unfun Cattail
。 - Christopher Schultz当我试图计算大约有10,000个文件夹,每个文件夹大约有10,000个文件的数据集中的文件数时,我来到了这里。许多方法的问题在于它们隐式地统计了1亿个文件,而这需要很长时间。
我采用了扩展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/*
-f
与 -A
(大写字母 'a')组合来解决:ls -f -A
。选项 -f
启用了 -a
(小写字母 'a'),但是它可以被 -A
覆盖。这已经在 ls
版本 8.30 中进行了测试。 - Peter Mortensen