CentOS 5.9
前几天我遇到一个问题,目录中有很多文件。为了数数,我跑了ls -l /foo/foo2/ | wc -l
事实证明,单个目录中有超过 100 万个文件(说来话长——根本原因正在得到解决)。
我的问题是:有没有更快的方法来进行计数?获得计数的最有效方法是什么?
答案1
简短回答:
\ls -afq | wc -l
(这包括.
和..
,因此减去 2。)
当您列出目录中的文件时,可能会发生三种常见情况:
- 枚举目录中的文件名。这是不可避免的:如果不枚举目录中的文件,就无法对它们进行计数。
- 对文件名进行排序。 Shell 通配符和
ls
命令可以做到这一点。 - 呼唤
stat
检索有关每个目录条目的元数据,例如它是否是目录。
#3 是迄今为止最昂贵的,因为它需要为每个文件加载一个 inode。相比之下,#1 所需的所有文件名都紧凑地存储在几个块中。 #2 浪费了一些 CPU 时间,但这通常不会破坏交易。
如果文件名中没有换行符,则简单地ls -A | wc -l
告诉您目录中有多少个文件。请注意,如果您有 的别名ls
,这可能会触发对 的调用stat
(例如ls --color
,ls -F
需要知道文件类型,这需要调用stat
),因此从命令行调用command ls -A | wc -l
或\ls -A | wc -l
以避免别名。
如果文件名中有换行符,是否列出换行符取决于 Unix 变体。 GNU coreutils 和 BusyBox 默认显示?
换行符,因此它们是安全的。
调用ls -f
以列出条目而不对它们进行排序 (#2)。这会自动打开-a
(至少在现代系统上)。该-f
选项在 POSIX 中但具有可选状态;大多数实现都支持它,但 BusyBox 不支持。该选项-q
将不可打印的字符(包括换行符)替换为?
;它是 POSIX,但 BusyBox 不支持,因此如果您需要 BusyBox 支持,则忽略它,但代价是名称包含换行符的文件数量过多。
如果目录没有子目录,那么大多数版本find
都不会调用stat
其条目(叶目录优化:链接计数为 2 的目录不能有子目录,因此find
不需要查找条目的元数据,除非条件如-type
需要)。这find . | wc -l
是一种可移植、快速的方法来计算目录中的文件数,前提是该目录没有子目录并且文件名不包含换行符。
如果目录没有子目录,但文件名可能包含换行符,请尝试其中之一(如果支持,第二个应该更快,但可能不会明显如此)。
find -print0 | tr -dc \\0 | wc -c
find -printf a | wc -c
另一方面,find
如果目录有子目录,则不要使用:甚至find . -maxdepth 1
调用stat
每个条目(至少使用 GNU find 和 BusyBox find)。您可以避免排序(#2),但要付出 inode 查找(#3)的代价,这会降低性能。
在没有外部工具的 shell 中,您可以使用 .run 对当前目录中的文件进行计数set -- *; echo $#
。这会错过点文件(名称以 开头的文件.
)并在空目录中报告 1 而不是 0。这是计算小目录中文件的最快方法,因为它不需要启动外部程序,但(zsh 除外)由于排序步骤(#2)而浪费了较大目录的时间。
在 bash 中,这是计算当前目录中文件数量的可靠方法:
shopt -s dotglob nullglob a=(*) echo ${#a[@]}
在 ksh93 中,这是计算当前目录中文件数量的可靠方法:
FIGNORE='@(.|..)' a=(~(N)*) echo ${#a[@]}
在 zsh 中,这是计算当前目录中文件数量的可靠方法:
a=(*(DNoN)) echo $#a
如果您
mark_dirs
设置了该选项,请确保将其关闭:a=(*(DNoN^M))
。在任何 POSIX shell 中,这是计算当前目录中文件数量的可靠方法:
total=0 set -- * if [ $# -ne 1 ] || [ -e "$1" ] || [ -L "$1" ]; then total=$((total+$#)); fi set -- .[!.]* if [ $# -ne 1 ] || [ -e "$1" ] || [ -L "$1" ]; then total=$((total+$#)); fi set -- ..?* if [ $# -ne 1 ] || [ -e "$1" ] || [ -L "$1" ]; then total=$((total+$#)); fi echo "$total"
除了 zsh 之外,所有这些方法都对文件名进行排序。
答案2
find /foo/foo2/ -maxdepth 1 | wc -l
在我的机器上速度要快得多,但本地.
目录会添加到计数中。
答案3
ls -1U
在管道应该花费更少的资源之前,因为它不会尝试对文件条目进行排序,它只是在磁盘上的文件夹中排序时读取它们。它还产生较少的输出,这意味着wc
.
您还可以使用ls -f
which或多或少是ls -1aU
.
我不知道是否有一种资源有效的方法可以通过命令来完成而不需要管道。
答案4
你可以尝试perl -e 'opendir($dh,".");$i=0;while(readdir $dh){$i++};print "$i\n";'
将时间与 shell 管道进行比较会很有趣。