web-dev-qa-db-ja.com

ディレクトリにあるファイルの数を数える最もリソース効率の良い方法は何ですか?

CentOS 5.9

先日、ディレクトリに多くのファイルがある問題に遭遇しました。それを数えるために、私はls -l /foo/foo2/ | wc -l

1つのディレクトリに100万を超えるファイルがあったことがわかります(長い話-根本的な原因は修正されています)。

私の質問は:カウントを行うためのより速い方法はありますか?カウントを取得する最も効率的な方法は何ですか?

57
Mike B

簡潔な答え:

_\ls -afq | wc -l
_

(これには_._および_.._が含まれるため、2を減算します。)


ディレクトリ内のファイルを一覧表示すると、3つの一般的なことが起こります。

  1. ディレクトリ内のファイル名を列挙します。これは避けられない:ディレクトリ内のファイルを列挙せずにカウントする方法はありません。
  2. ファイル名をソートします。シェルのワイルドカードとlsコマンドがそれを行います。
  3. 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

私のマシンではかなり高速ですが、ローカル.ディレクトリがカウントに追加されます。

17
Joel Taylor

ls -1Uは、パイプがリソースを少しだけ消費する前に、ファイルエントリを並べ替えようとせず、ディスク上のフォルダで並べ替えられたときにそれらを読み取るだけです。また、生成される出力も少なくなります。つまり、wcの処理が少し少なくなります。

ls -fのショートカットでもあるls -1aUを使用することもできます。

しかし、パイプを使わずにコマンドを使用してリソース効率の良い方法があるかどうかはわかりません。

8
Luis Machuca

比較の別のポイント。シェルのワンライナーではありませんが、この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);
}
6
Thomas Nyman

あなたはPerl -e 'opendir($dh,".");$i=0;while(readdir $dh){$i++};print "$i\n";'を試すことができます

タイミングをシェルパイプと比較すると興味深いでしょう。

3
doneal24

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
2
Ramesh

外部プログラムを必要としないbashのみのソリューションですが、どれほど効率的かわかりません。

list=(*)
echo "${#list[@]}"
1
enzotib

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 
1
indrajeet

おそらく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
)
1
mikeserv

カウントからサブディレクトリを除外するには、Gillesからの承認された回答のバリエーションを次に示します。

_echo $(( $( \ls -afq target | wc -l ) - $( \ls -od target | cut -f2 -d' ') ))
_

外側の$(( ))算術展開は、最初の$( )から2番目の$( )サブシェルの出力を減算します。最初の$( )は、上からのGillesです。 2番目の$( )は、ターゲットに「リンク」しているディレクトリの数を出力します。これは_ls -od_(必要に応じて_ls -ld_に置き換えます)から取得されます。ハードリンクの数をリストする列には、ディレクトリの特別な意味があります。 「リンク」数には、_._、_.._、およびすべてのサブディレクトリが含まれます。

私はパフォーマンスをテストしませんでしたが、同じように見えます。ターゲットディレクトリの統計情報と、追加されたサブシェルとパイプのオーバーヘッドが追加されます。

0
user361782

@Joelの回答から問題を修正した後、.ファイルとして:

find /foo/foo2 -maxdepth 1 | tail -n +2 | wc -l

tailは、最初の行を削除するだけです。つまり、.はもうカウントされません。

0
haneefmubarak

ls -1 | wc -lはすぐに思い浮かびます。 ls -1Uls -1より高速であるかどうかは、純粋に学術的なものです。違いはごくわずかですが、非常に大きなディレクトリの場合です。

0
countermode

私はこれが古いことを知っていますが、awkhasがここで言及されると思います。 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/)
0
user.friendly

少し遅れた回答(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 ..)
0
jm666