非常に多数のファイル(> 100,000)がある場合に、特定のディレクトリ内のファイルの数を見つけるための最良の方法を見つけようとしています。
その数のファイルがある場合、実行にls | wc -l
はかなり長い時間がかかります。これは、すべてのファイルの名前を返すためだと思います。ディスクI / Oをできるだけ少なくしようとしています。
私はいくつかのシェルとPerlスクリプトを使って実験してみましたが、役に立ちませんでした。何か案は?
非常に多数のファイル(> 100,000)がある場合に、特定のディレクトリ内のファイルの数を見つけるための最良の方法を見つけようとしています。
その数のファイルがある場合、実行にls | wc -l
はかなり長い時間がかかります。これは、すべてのファイルの名前を返すためだと思います。ディスクI / Oをできるだけ少なくしようとしています。
私はいくつかのシェルとPerlスクリプトを使って実験してみましたが、役に立ちませんでした。何か案は?
回答:
デフォルトで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
最後の1つはdircnt
、上記のソースからコンパイルされたプログラムです。
編集2016-09-26
多くの要望があるため、このプログラムは再帰的になるように書き直したので、サブディレクトリにドロップされ、ファイルとディレクトリを別々にカウントし続けます。
一部の人々がこれをすべて行う方法を知りたいのは明らかなので、何が起こっているのかを明確にしようとするために、コードにはたくさんのコメントがあります。私はこれを書いて64ビットLinuxでテストしましたが、Microsoft Windowsを含むPOSIX準拠のシステムで動作するはずです。バグ報告は大歓迎です。AIXまたはOS / 400などで動作しない場合は、更新していただければ幸いです。
ご覧のように、元のコードよりもはるかに複雑であり、必ずそうです。コードを非常に複雑にしたくない場合(たとえば、サブディレクトリスタックの管理と1つのループでの処理など)を除き、少なくとも1つの関数を再帰的に呼び出す必要があります。ファイルの種類をチェックする必要があるため、異なるOS間の違い、標準ライブラリなどが関係するので、コンパイルするすべてのシステムで使用できるようにするプログラムを記述しました。
エラーチェックはほとんどなく、count
関数自体は実際にはエラーを報告しません。本当に失敗する可能性がある唯一の呼び出しはopendir
andです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によって提案された2つの変更を組み込みました。
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%高速で、後続の(キャッシュ)実行を比較すると少なくとも2倍高速です。
find
いうよりls
は理由が早いようですls
。あなたは、ソート停止、場合ls
とfind
同様の性能を持っています。
検索、ls、およびperlは40 000ファイルに対してテストされました。同じ速度です(ただし、キャッシュをクリアしようとはしませんでした)。
[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よりも2倍遅くなります。
[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インチHDDのフォルダーで初めて実行すると、操作が完了するまでに約3分かかります。2回目に12秒のIIRCがかかります。また、これはファイルシステムにも依存する可能性があります。を使用してい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の1行です。
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
もちろん、小数点以下3桁目の値は、これらを実行するたびに少しシフトするため、基本的には同じです。ただしfind
、実際のディレクトリ自体をカウントするため、1つの余分な単位を返すことに注意してください(前述のように、ls -f
。と..もカウントするため、2つの余分な単位を返します)。
完全を期すためにこれを追加します。もちろん、正解はすでに他の誰かによって投稿されていますが、ツリープログラムを使用してファイルとディレクトリの数を取得することもできます。
コマンドtree | tail -n 1
を実行して、「763ディレクトリ、9290ファイル」のような最終行を取得します。これは、フラグで追加できる隠しファイルを除いて、ファイルとフォルダーを再帰的にカウントします-a
。参考までに、私のコンピュータでは、ツリーがホームディレクトリ全体(24777ディレクトリ、238680ファイル)をカウントするのに4.8秒かかりました。find -type f | wc -l
5.3秒、0.5秒長くかかったので、ツリーは速度に関してかなり競争力があると思います。
サブフォルダーがない限り、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()
中にはPerl
高速です。これらの関数の例については、こちらをご覧ください
ここでのこの回答は、非常に大きく、ネストされたディレクトリについて、このページの他のほとんどすべてよりも高速です。
https://serverfault.com/a/691372/84703
locate -r '.' | grep -c "^$PWD"
私はここに来て、それぞれ〜1万個のファイルを含む〜1万個のフォルダのデータセット内のファイルを数えようと試みました。アプローチの多くに伴う問題は、それらが暗黙的に100Mファイルをstatすることです。
私は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フォルダー内の1億個のファイルでは、上記の処理は非常に短時間で完了します(初回実行で約5分、キャッシュでのフォローアップ:約23秒)。
1時間未満で終了した他の唯一のアプローチは、キャッシュに約1分あるls ls -f /your/dirs/* | wc -l
でした。カウントはディレクトリごとにいくつかの改行でオフです...
予想外に、私の試みfind
は1時間以内に戻ってきませんでした:-/
Linuxでの最速の方法(質問にはlinuxのタグが付けられています)は、直接システムコールを使用することです。以下は、ディレクトリ内のファイル(dirsのみ)をカウントする小さなプログラムです。数百万のファイルをカウントでき、 "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:それは再帰的ではありませんが、それを達成するために修正することができます。
opendir
/ readdir
で行うすべてのことをトレースしたわけではありませんが、結局のところ、ほぼ同じコードになると思います。そのようにシステムコールを行うことも移植性がなく、Linux ABIが安定していないため、あるシステムでコンパイルされたプログラムが別のシステムで正しく動作することは保証されていません(ただし、* NIXシステムIMOのソースから何かをコンパイルすることはかなり良いアドバイスです) )。速度が重要である場合、これが実際に速度を向上させるのであれば、これは良い解決策です-プログラムを個別にベンチマークしていません。
大量のデータがある場合にメモリ処理を使用しないほうが、コマンドを「パイプ」するよりも速いことに気づきました。結果をファイルに保存し、分析した後
ls -1 /path/to/dir > count.txt && cat count.txt | wc -l
ls / findの代わりに「getdents」を使用する必要があります
getdentsのアプローチを説明した非常に優れた記事が1つあります。
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()に依存するツールよりも深く掘り下げる必要があります。libcのヘルパーメソッドではなく、getdents()システムコールを直接使用する必要があります。
ここからgetdents()を使用してファイルをリストするCコードを見つけることができます:
ディレクトリ内のすべてのファイルをすばやく一覧表示するには、2つの変更が必要です。
最初に、バッファサイズをXから5 MBなどに増やします。
#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.01秒に数千のファイルまたは数百万とフォルダを終了しますか?ls
他のソリューションと比較して、あなたも非常に非効率的です。そしてOPは、カウントを取得したいだけで、そこに座って出力の変化を見ていない
watch
そのコメントの後にマニュアルを読みましたが、ほとんどのPC画面のリフレッシュレートは60Hzに過ぎないため、0.01秒(0.1秒ではない)は非現実的な数値であることがわかりました。OPは、「多数のファイルの高速Linuxファイル数」について尋ねました。また、投稿前に利用可能な回答を読んでいませんでした
ファイル数が最も多い最初の10のディレクター。
dir=/ ; for i in $(ls -1 ${dir} | sort -n) ; { echo "$(find ${dir}${i} \
-type f | wc -l) => $i,"; } | sort -nr | head -10