CentOS 5.9
先日、ディレクトリに多くのファイルがある問題に遭遇しました。それを数えるために、私はls -l /foo/foo2/ | wc -l
1つのディレクトリに100万を超えるファイルがあったことがわかります(長い話-根本的な原因は修正されています)。
私の質問は:カウントを行うためのより速い方法はありますか?カウントを取得する最も効率的な方法は何ですか?
簡潔な答え:
_\ls -afq | wc -l
_
(これには_.
_および_..
_が含まれるため、2を減算します。)
ディレクトリ内のファイルを一覧表示すると、3つの一般的なことが起こります。
ls
コマンドがそれを行います。stat
を呼び出して、ディレクトリかどうかなど、各ディレクトリエントリに関するメタデータを取得します。#3は、ファイルごとにiノードをロードする必要があるため、はるかに高価です。比較すると、#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
_は、ディレクトリにサブディレクトリがなく、ファイル名に改行が含まれていない場合に、ディレクトリ内のファイルをカウントするポータブルで高速な方法です。
ディレクトリにサブディレクトリはないがファイル名に改行が含まれている可能性がある場合は、次のいずれかを試してください(2番目のサポートは、サポートされている場合は高速ですが、それほどサポートされていない場合があります)。
_find -print0 | tr -dc \\0 | wc -c
find -printf a | wc -c
_
一方、ディレクトリにサブディレクトリがある場合は、find
を使用しないでください。_find . -maxdepth 1
_でも、すべてのエントリでstat
を呼び出します(少なくともGNU = findとBusyBox find)ソートは避け(#2)、iノードルックアップ(#3)の代償を払ってパフォーマンスを低下させます。
外部ツールを使用しないシェルでは、_set -- *; echo $#
_を使用して、現在のディレクトリにあるファイルをcount実行できます。これは、ドットファイル(名前が_.
_で始まるファイル)を検出せず、空のディレクトリで0ではなく1を報告します。これは、外部プログラムを起動する必要がないため、小さなディレクトリでファイルをカウントする最も速い方法ですが、(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シェルでも、これは現在のディレクトリ内のファイルをカウントする信頼できる方法です。
_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以外のすべてのメソッドは、ファイル名をソートします。
find /foo/foo2/ -maxdepth 1 | wc -l
私のマシンではかなり高速ですが、ローカル.
ディレクトリがカウントに追加されます。
ls -1U
は、パイプがリソースを少しだけ消費する前に、ファイルエントリを並べ替えようとせず、ディスク上のフォルダで並べ替えられたときにそれらを読み取るだけです。また、生成される出力も少なくなります。つまり、wc
の処理が少し少なくなります。
ls -f
のショートカットでもあるls -1aU
を使用することもできます。
しかし、パイプを使わずにコマンドを使用してリソース効率の良い方法があるかどうかはわかりません。
比較の別のポイント。シェルのワンライナーではありませんが、このCプログラムは余分なことは何もしません。隠しファイルはls|wc -l
の出力と一致するように無視されることに注意してください(出力の最初の行の合計ブロックのため、ls -l|wc -l
は1つずつオフになっています)。
#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
#include <error.h>
#include <errno.h>
int main(int argc, char *argv[])
{
int file_count = 0;
DIR * dirp;
struct dirent * entry;
if (argc < 2)
error(EXIT_FAILURE, 0, "missing argument");
if(!(dirp = opendir(argv[1])))
error(EXIT_FAILURE, errno, "could not open '%s'", argv[1]);
while ((entry = readdir(dirp)) != NULL) {
if (entry->d_name[0] == '.') { /* ignore hidden files */
continue;
}
file_count++;
}
closedir(dirp);
printf("%d\n", file_count);
}
あなたはPerl -e 'opendir($dh,".");$i=0;while(readdir $dh){$i++};print "$i\n";'
を試すことができます
タイミングをシェルパイプと比較すると興味深いでしょう。
this answer から、これは可能な解決策と考えることができます。
/*
* List directories using getdents() because ls, find and Python libraries
* use readdir() which is slower (but uses getdents() underneath.
*
* Compile with
* ]$ gcc getdents.c -o getdents
*/
#define _GNU_SOURCE
#include <dirent.h> /* Defines DT_* constants */
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#define handle_error(msg) \
do { perror(msg); exit(EXIT_FAILURE); } while (0)
struct linux_dirent {
long d_ino;
off_t d_off;
unsigned short d_reclen;
char d_name[];
};
#define BUF_SIZE 1024*1024*5
int
main(int argc, char *argv[])
{
int fd, nread;
char buf[BUF_SIZE];
struct linux_dirent *d;
int bpos;
char d_type;
fd = open(argc > 1 ? argv[1] : ".", O_RDONLY | O_DIRECTORY);
if (fd == -1)
handle_error("open");
for ( ; ; ) {
nread = syscall(SYS_getdents, fd, buf, BUF_SIZE);
if (nread == -1)
handle_error("getdents");
if (nread == 0)
break;
for (bpos = 0; bpos < nread;) {
d = (struct linux_dirent *) (buf + bpos);
d_type = *(buf + bpos + d->d_reclen - 1);
if( d->d_ino != 0 && d_type == DT_REG ) {
printf("%s\n", (char *)d->d_name );
}
bpos += d->d_reclen;
}
}
exit(EXIT_SUCCESS);
}
上記のCプログラムを、ファイルをリストする必要があるディレクトリにコピーします。次に、次のコマンドを実行します。
gcc getdents.c -o getdents
./getdents | wc -l
外部プログラムを必要としないbashのみのソリューションですが、どれほど効率的かわかりません。
list=(*)
echo "${#list[@]}"
pythonのos.listdir()はあなたのために仕事をすることができます。それは特別な '。'と '..'ファイルを除いてディレクトリの内容の配列を与えます。また、いいえ名前に「\ n」のような特殊文字が含まれるabtファイルを心配する必要があります。
python -c 'import os;print len(os.listdir("."))'
以下は、上記のpythonコマンドと「ls -Af」コマンドを比較した場合の所要時間です。
〜/ test $ time ls -Af | wc -l 399144 real 0m0.300s user 0m0.104s sys 0m0.240s 〜/ test $ time python -c 'import os; print len(os.listdir( "。"))' 399142 実際の0m0.249s ユーザー0m0.064s sys 0m0.180s
おそらくmostリソースを効率的に使用する方法には、外部プロセスの呼び出しは含まれません。だから私は賭けます...
cglb() ( c=0 ; set --
tglb() { [ -e "$2" ] || [ -L "$2" ] &&
c=$(($c+$#-1))
}
for glb in '.?*' \*
do tglb $1 ${glb##.*} ${glb#\*}
set -- ..
done
echo $c
)
カウントからサブディレクトリを除外するには、Gillesからの承認された回答のバリエーションを次に示します。
_echo $(( $( \ls -afq target | wc -l ) - $( \ls -od target | cut -f2 -d' ') ))
_
外側の$(( ))
算術展開は、最初の$( )
から2番目の$( )
サブシェルの出力を減算します。最初の$( )
は、上からのGillesです。 2番目の$( )
は、ターゲットに「リンク」しているディレクトリの数を出力します。これは_ls -od
_(必要に応じて_ls -ld
_に置き換えます)から取得されます。ハードリンクの数をリストする列には、ディレクトリの特別な意味があります。 「リンク」数には、_.
_、_..
_、およびすべてのサブディレクトリが含まれます。
私はパフォーマンスをテストしませんでしたが、同じように見えます。ターゲットディレクトリの統計情報と、追加されたサブシェルとパイプのオーバーヘッドが追加されます。
@Joelの回答から問題を修正した後、.
ファイルとして:
find /foo/foo2 -maxdepth 1 | tail -n +2 | wc -l
tail
は、最初の行を削除するだけです。つまり、.
はもうカウントされません。
ls -1 | wc -l
はすぐに思い浮かびます。 ls -1U
がls -1
より高速であるかどうかは、純粋に学術的なものです。違いはごくわずかですが、非常に大きなディレクトリの場合です。
私はこれが古いことを知っていますが、awk
hasがここで言及されると思います。 wc
の使用を含む提案は、OPの質問「最もリソース効率の良い方法」に関しては正しくありません。最近、ログファイルが(いくつかの悪いソフトウェアが原因で)制御不能になるため、この投稿に遭遇しました。およそ2億3200万のエントリーがありました!私が最初に試しましたwc -l
と15分待った-行のカウントを完了することさえできませんでした。次のawk
ステートメントは、そのログファイルの3分の正確な行数を教えてくれました。私は長年にわたり、標準のシェルプログラムをはるかに効率的にシミュレートするawkの能力を過小評価しないことを学びました。
awk 'BEGIN{i=0} {i++} END{print i}' /foo/foo2
そして、ディレクトリ内のファイルをカウントするためにls
のようなコマンドを置き換える必要がある場合:
`#Normal:` awk 'BEGIN{i=0} {i++} END{print i}' <(ls /foo/foo2/)
`#Hidden:` awk 'BEGIN{i=0} {i++} END{print (i-2)}' <(ls -f /foo/foo2/)
少し遅れた回答(6年後)ですが...
最速の方法は単にls -l
親ディレクトリを実行し、指定されたサブディレクトリのリンク数列を確認することです。
デモ:たとえば、/usr/lib
ディレクトリ内のファイル/ディレクトリの数を数えたいとしましょう。
したがって、ls -l /usr
と入力すると、以下が生成されます。
total 0
drwxr-xr-x 978 root wheel 31296 29 apr 2019 bin
drwxr-xr-x 267 root wheel 8544 30 okt 2018 include
drwxr-xr-x 312 root wheel 9984 23 jan 2019 lib
drwxr-xr-x 240 root wheel 7680 29 apr 2019 libexec
drwxr-xr-x 17 root wheel 544 14 nov 2018 local
drwxr-xr-x 248 root wheel 7936 23 jan 2019 sbin
drwxr-xr-x 47 root wheel 1504 4 okt 2018 share
drwxr-xr-x 5 root wheel 160 25 okt 2017 standalone
権限の直後の番号は、ファイルのlink count
です。ディレクトリの場合、それはその中のエントリの数です。したがって、上記の例では、/usr/lib
には12エントリがあります。
確認しましょう:
$ ls -1a /usr/lib | wc -l
312
親の他のディレクトリを表示せずに、単に-d
を使用します。
$ ls -ld /usr/lib
drwxr-xr-x 312 root wheel 9984 23 jan 2019 /usr/lib
# ^^^ - the number of entries in the /usr/lib (including . and ..)