CentOS 5.9
先日、ディレクトリに多くのファイルがある問題に遭遇しました。それを数えるために、私は走ったls -l /foo/foo2/ | wc -l
1つのディレクトリに100万を超えるファイルがあったことがわかります(長い話-根本的な原因は修正されつつあります)。
私の質問は次のとおりです。カウントを行うより速い方法はありますか?カウントを取得する最も効率的な方法は何でしょうか?
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 $#
。これにより、ドットファイル(名前がで始まるファイル.
)が欠落し、空のディレクトリで0ではなく1が報告されます。これは、外部プログラムを起動する必要がないため、小さなディレクトリ内のファイルをカウントする最速の方法ですが、ソート手順(#2)のために(zshを除き)大きなディレクトリの時間を無駄にします。
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 -maxdepth 1
簡単に対応できます。GNU findは実際に呼び出しますか?ファイルの詳細を返すように設定した場合、どれだけ遅くなるかと比較しても、速度は低下しません。一方、明確な速度の勝者は、非ソートグロブを使用しています。(ソートされたグロブは、ソートされていないグロブが2倍高速であるのに対し、2倍遅いです)。ファイルシステムの種類がこれらの結果に大きな影響を与えるのではないかと思います。\ls -U
-type
stat
find -type
ls -l
zsh
ls
strace
。これは、ディレクトリにサブディレクトリがある場合にのみ当てはまります。それ以外find
の場合、リーフディレクトリの最適化が有効になります(なしでも-maxdepth 1
)。ファイルシステムのタイプ(stat
ディレクトリをツリーとして表すファイルシステムよりもディレクトリを線形リストとして表すファイルシステムの方が呼び出しにかかるコストが高い)、iノードがすべて一緒に作成されて近くにあるかどうかなど、多くのことが結果に影響する可能性がありますディスク、コールドキャッシュまたはホットキャッシュなど
ls -f
は、呼び出しを防ぐための信頼できる方法stat
でした-これは、今日では単に「出力がソートされていない」(これも原因となる)と説明され、andが含まれ.
てい..
ます。-A
そして-U
標準オプションではありません。
\ls -afq *[0-9].pdb | wc -l
version sh (AT&T Research) 93u+ 2012-08-01
参考までに、Debianベースのシステムでksh93を使用 してFIGNORE
いると、うまくいかないようです。.
そして..
エントリが得られた配列に含まれている
find /foo/foo2/ -maxdepth 1 | wc -l
私のマシンではかなり高速ですが、ローカル.
ディレクトリがカウントに追加されます。
-type
、パラメータをfind
より速くする必要がありますls
-mindepth 1
ディレクトリ自体を省略するには、a を追加します。
別の比較ポイント。シェルのワンライナーではありませんが、この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);
}
readdir()
、いくつかのオーバーヘッドを追加しないstdioのAPIを、あなたは(基本となるシステムコールに渡されたバッファのサイズを制御できていないgetdents
Linux上)
試すことができます perl -e 'opendir($dh,".");$i=0;while(readdir $dh){$i++};print "$i\n";'
タイミングをシェルパイプと比較するのは興味深いことです。
find -maxdepth 1 | wc -l
、\ls -AU | wc -l
そしてzsh
ベースの非ソートグロブと配列数)。つまり、並べ替えや無関係なファイルプロパティの読み取りなど、さまざまな非効率なオプションを打ち破ります。それはあなたにも何も得られないので、私は言いたいと思います、あなたがすでにperlにいるのでない限り、より簡単なソリューションを使用する価値はありません:)
.
との..
ディレクトリエントリが含まれるため、実際のファイル数(およびサブディレクトリ)を取得するには2を引く必要があります。現代のPerlでは、perl -E 'opendir $dh, "."; $i++ while readdir $dh; say $i - 2'
それを行います。
この答えから、私はこれを可能な解決策と考えることができます。
/*
* 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
ls -f
上でフィルタリングしない、d_type
ちょうど上の、すべてでd->d_ino != 0
。3).
とに対して2を引き..
ます。
ls -f
。
外部プログラムを必要としないbashのみのソリューションですが、どれほど効率的かわかりません:
list=(*)
echo "${#list[@]}"
@Joelの回答から問題を修正した後.
、ファイルとして追加しました:
find /foo/foo2 -maxdepth 1 | tail -n +2 | wc -l
tail
最初の行を削除するだけ.
です。つまり、もうカウントされません。
let count = $(find /foo/foo2 -maxdepth 1 | wc -l) - 2
pythonのos.listdir()はあなたのために仕事をすることができます。特別な「。」を除く、ディレクトリのコンテンツの配列を提供します および「..」ファイル。また、名前に「\ n」などの特殊文字が含まれるabtファイルを心配する必要はありません。
python -c 'import os;print len(os.listdir("."))'
以下は、「ls -Af」コマンドと比較した上記のpythonコマンドの所要時間です。
〜/ test $ time ls -Af | wc -l 399144 実際の0m0.300s ユーザー0m0.104s sys 0m0.240s 〜/ test $ time python -c 'import os; print len(os.listdir( "。"))' 399142 実際の0m0.249s ユーザー0m0.064s sys 0m0.180s
カウントからサブディレクトリを除外するために、Gillesが受け入れた回答のバリエーションを次に示します。
echo $(( $( \ls -afq target | wc -l ) - $( \ls -od target | cut -f2 -d' ') ))
外側の$(( ))
算術展開$( )
は、最初のサブシェルから2番目のサブシェルの出力を減算します$( )
。1つ目$( )
は、上から正確にジルズです。2番目$( )
は、ターゲットに「リンク」しているディレクトリの数を出力します。これはls -od
(ls -ld
必要に応じて置換)、ハードリンクの数を一覧表示する列がディレクトリの特別な意味として持つ場合に由来します。「リンク」カウントが含まれ.
、..
および任意のサブディレクトリ。
パフォーマンスはテストしませんでしたが、似ているようです。ターゲットディレクトリの統計情報と、追加されたサブシェルとパイプのオーバーヘッドが追加されます。
echo *はどの「ls」コマンドよりも効率的だと思います。
echo * | wc -w
echo 'Hello World'|wc -w
を生成し2
ます。
ls -l|wc -l
原因の第一行中の総ブロックにいずれかによってオフになりls -l
、出力