web-dev-qa-db-ja.com

多数のファイルの高速Linuxファイルカウント

非常に多数のファイル(> 100,000)がある場合に、特定のディレクトリにあるファイルの数を見つける最良の方法を見つけようとしています。

その数のファイルがある場合、「ls | wc -l」を実行すると実行に非常に長い時間がかかります。これは、すべてのファイルの名前を返すためだと思います。ディスクをできるだけ少なくしようとしていますIO。

いくつかのシェルおよびPerlスクリプトを試してみましたが、役に立ちませんでした。何か案は?

114
Charles

デフォルトでは、lsは名前を並べ替えますが、名前が多数ある場合は時間がかかることがあります。また、すべての名前が読み取られてソートされるまで、出力はありません。 ls -fオプションを使用して、ソートをオフにします。

ls -f | wc -l

これにより-aも有効になるため、...、および.で始まるその他のファイルがカウントされることに注意してください。

166
mark4o

最速の方法は、次のような専用プログラムです。

#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は、上記のソースからコンパイルされたプログラムです。

EDIT 2016-09-26

人気のある需要のため、このプログラムを再帰的に書き換えて、サブディレクトリにドロップし、ファイルとディレクトリを個別にカウントし続けるようにしました。

一部の人々がこれをすべて行う方法howを知りたいのは明らかであるため、何が起こっているのかを明確にするためにコードに多くのコメントがあります。これを書いて64ビットLinuxでテストしましたが、shouldはMicrosoft Windowsを含むPOSIX準拠のシステムで動作するはずです。バグ報告は大歓迎です。ご使用のAIXまたはOS/400などで動作しない場合は、これを更新できてうれしいです。

あなたが見ることができるように、それはオリジナルよりもはるかにより複雑であり、必然的に:コードを非常に複雑にしたくない限り、少なくとも1つの関数が再帰的に呼び出されるために存在しなければなりませんサブディレクトリスタックと単一ループでの処理)。ファイルの種類を確認する必要があるため、OS、標準ライブラリなどの違いが関係するので、コンパイルするシステムで使用できるプログラムを作成しました。

エラーチェックはほとんどなく、count関数自体は実際にはエラーを報告しません。本当に失敗する可能性のある呼び出しはopendirstatだけです(運が良くなく、direntに既にファイルタイプが含まれているシステムがある場合)。サブディレクトリのパス名の合計の長さをチェックするのは偏執的ではありませんが、理論的には、システムは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;
}

EDIT 2017-01-17

@FlyingCodeMonkeyによって提案された2つの変更を組み込みました。

  1. lstatの代わりにstatを使用します。スキャンするディレクトリにシンボリックリンクされたディレクトリがある場合、これによりプログラムの動作が変更されます。以前の動作では、(リンクされた)サブディレクトリのファイル数が全体数に追加されていました。新しい動作では、リンクされたディレクトリは単一のファイルとしてカウントされ、その内容はカウントされません。
  2. ファイルのパスが長すぎる場合、エラーメッセージが表示され、プログラムが停止します。

EDIT 2017-06-29

運が良ければ、これはこの回答のlast編集になります:)

このコードを GitHubリポジトリ にコピーして、コードを取得しやすくしました(コピー/貼り付けの代わりに、 ソースのダウンロード )、さらにGitHubからpull-requestを送信することで、誰でも簡単に修正を提案できます。

ソースはApache License 2.0で利用可能です。パッチ* ようこそ!


  • 「パッチ」は、私のような高齢者が「プルリクエスト」と呼ぶものです。
55

見つけようとしましたか?例えば:

find . -name "*.ext" | wc -l
33
igustin

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を使用して、エイリアスオプションをバイパスしたことを確認しました。このオプションは、might少し遅くなり、ファイルの順序付けを避けるために-fです。 -fなしのlsは、lsが-fとともに使用されている場合を除き、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の回答に基づくテスト。

トーマス

17
Thomas

要件に基づいて出力を変更できますが、ここでは、一連の数値的に名前が付けられたディレクトリ内のファイルの数を再帰的にカウントおよびレポートするために作成した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,
5
mightybs

驚いたことに、ベアボーン検索はls -fと非常に似ています

> time ls -f my_dir | wc -l
17626

real    0m0.015s
user    0m0.011s
sys     0m0.009s

versus

> time find my_dir -maxdepth 1 | wc -l
17625

real    0m0.014s
user    0m0.008s
sys     0m0.010s

もちろん、小数点以下3桁の値は、これらのいずれかを実行するたびに少しシフトするため、基本的に同じです。ただし、findは実際のディレクトリ自体をカウントするため、1つの余分なユニットを返します(前述のように、ls -fは2つの余分な単位を返します。および..)。

4

完全を期すためにこれを追加するだけです。もちろん、正解は他の誰かによってすでに投稿されていますが、ツリープログラムを使用してファイルとディレクトリの数を取得することもできます。

コマンドtree | tail -n 1を実行して、最後の行を取得します。これは、「763ディレクトリ、9290ファイル」のようなものです。これは、-aフラグで追加できる隠しファイルを除いて、ファイルとフォルダーを再帰的にカウントします。参考までに、私のコンピューターで4.8秒かかりました。これは、24777のディレクトリ、238680のファイルであるホームディレクトリ全体をツリーがカウントするために必要でした。 find -type f | wc -lは5.3秒かかり、0.5秒長くなりました。そのため、ツリーはスピード的にかなり競争力があると思います。

サブフォルダーがない限り、ツリーはファイルを数える簡単で簡単な方法です。

また、単にその楽しみのために、tree | grep '^├'を使用して、現在のディレクトリ内のファイル/フォルダーのみを表示できます。これは、基本的にlsのはるかに遅いバージョンです。

3
Benubird

高速Linuxファイル数

私が知っている最速のLinuxファイル数は

locate -c -r '/home'

no grepを呼び出す必要があります!ただし、前述のように、新鮮なデータベース(cronジョブによって毎日更新されるか、Sudo updatedb)。

man Locateから

-c, --count
    Instead  of  writing  file  names on standard output, write the number of matching
    entries only.

追加また、ディレクトリをファイルとしてカウントすることを知っておく必要があります!


BTW:システムタイプのファイルとディレクトリの概要が必要な場合

locate -S

ディレクトリ、ファイルなどの数を出力します。

2
abu_bua

私は答えにコメントに十分な評判ポイントがないのでこれをここに書いていますが、私は自分自身を残すことができます答え、それは意味をなさない。とにかく...

Christopher Schultzによる回答 に関して、statlstatバッファオーバーフローを回避するために境界チェックを追加する可能性があります:

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を使用することの提案は、ディレクトリに親ディレクトリへのシンボリックリンクが含まれている場合、サイクルにつながる可能性があるシンボリックリンクをたどらないようにすることです。

2

ここでのこの回答は、非常に大きく、非常にネストされたディレクトリの場合、このページの他のほとんどすべてよりも高速です。

https://serverfault.com/a/691372/847

locate -r '.' | grep -c "^$PWD"

2
ck_

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:それは再帰的ではありませんが、それを達成するために修正することができます。

2

Perlopendir()およびreaddir()を使用した方が速い場合は試してみてください。これらの関数の例については here

〜10K個のファイルを含む〜10K個のフォルダーのデータセット内のファイルをカウントしようとしたときにここに来ました。多くのアプローチの問題は、暗黙的に100Mファイルを統計することであり、これには時間がかかります。

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時間未満で終了した他の唯一のアプローチはlsで、キャッシュは約1分でした:ls -f /your/dirs/* | wc -l。ただし、ディレクトリごとに数行の改行がカウントされます...

予想外に、findを使った私の試みは1時間以内に戻りませんでした:-/

2
Jörn Hees

lsは、-fソートを無効にすると、時間が節約されます。

ls -f | wc -l

または、findを使用できます。

find . -type f | wc -l
1
Mohammad Anini

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()syscallを直接使用する必要があります。

here のgetdents()を使用して、ファイルをリストするCコードを見つけることができます。

ディレクトリ内のすべてのファイルをすばやく一覧表示するには、2つの変更が必要です。

まず、バッファサイズをXから5メガバイトなどに増やします。

#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]
0
Dev123

大量のデータがあるときにメモリ処理を使用しないほうが、コマンドを「パイプする」よりも速いことに気付きました。結果をファイルに保存し、分析した後

ls -1 /path/to/dir > count.txt && cat count.txt | wc -l
0