ファイルシステム全体で文字列を含むファイルを見つけるには、どちらがより効率的ですか。ファイル拡張子またはファイル名と一致する正規表現がわかっていて、-type f
どっちがいい? GNU grep 2.6.3; find(GNU findutils)4.4.2
例:
grep -r -i 'the brown dog' /
find / -type f -exec grep -i 'the brown dog' {} \;
よく分かりません:
grep -r -i 'the brown dog' /*
本当にあなたが意味したことです。これは、/
のすべての非表示ではないファイルとdirsでgrepを再帰的に実行することを意味します(ただし、非表示のファイルとその内部のdirsを引き続き確認します)。
あなたが意味したと仮定すると:
grep -r -i 'the brown dog' /
注意すべきいくつかの点:
grep
実装が-r
をサポートしているわけではありません。そして、そうするものの間で、動作が異なります。ディレクトリツリーをトラバースするときに、ディレクトリへのシンボリックリンクをたどる場合(つまり、同じファイルを何度も検索してしまう場合や、無限ループで実行される場合もあります)、そうでないものもあります。一部のデバイスは、デバイスファイル(たとえば、/dev/zero
ではかなり時間がかかります)またはパイプまたはバイナリファイルを調べますが、そうでないものもあります。grep
は、ファイルを検出するとすぐにファイル内の検索を開始するので、効率的です。しかし、ファイルを検索している間、検索するファイルをこれ以上検索しません(ほとんどの場合、これはおそらく同じです)。君の:
find / -type f -exec grep -i 'the brown dog' {} \;
(ここでは意味のない-r
を削除しました)ファイルごとに1つのgrep
を実行しているため、非常に非効率的です。 ;
は、引数を1つだけ受け入れるコマンドにのみ使用してください。さらに、ここではgrep
は1つのファイルのみを検索するため、ファイル名は出力されないため、一致する場所がわかりません。
デバイスファイル、パイプ、シンボリックリンクなどを調べていません。シンボリックリンクをたどっていませんが、/proc/mem
などの内部を調べている可能性があります。
find / -type f -exec grep -i 'the brown dog' {} +
可能な限り少ないgrep
コマンドが実行されるため、はるかに優れています。前回の実行でファイルが1つしかない場合を除き、ファイル名を取得します。そのためには、次のように使用することをお勧めします。
find / -type f -exec grep -i 'the brown dog' /dev/null {} +
またはGNU grep
:
find / -type f -exec grep -Hi 'the brown dog' {} +
grep
は、find
が噛むのに十分なファイルを見つけるまで開始されないため、初期遅延が発生することに注意してください。また、find
は、前のgrep
が返されるまで、ファイルの検索を続行しません。大きなファイルリストを割り当てて渡すことは、(おそらく無視できる)影響を与えるため、シンボリックリンクをたどらないか、デバイスの内部を調べないgrep -r
よりも、全体的に効率が悪くなります。
GNUツール:
find / -type f -print0 | xargs -r0 grep -Hi 'the brown dog'
上記のように、実行されるgrep
インスタンスはできるだけ少なくしますが、find
は、最初のgrep
呼び出しが最初のバッチ内を検索している間に、さらにファイルを検索し続けます。それは利点かもしれませんし、そうでないかもしれません。たとえば、データが回転ハードドライブに保存されている場合、find
およびgrep
がディスクの異なる場所に保存されているデータにアクセスすると、ディスクヘッドが常に移動するため、ディスクのスループットが低下します。 RAIDセットアップ(find
とgrep
は異なるディスクにアクセスする可能性があります)またはSSDで、それは良い違いをもたらすかもしれません。
RAIDセットアップで、いくつかのconcurrentgrep
呼び出しを実行すると、状況が改善される場合があります。 GNU 3つのディスクを持つRAID1ストレージ上のツールで、
find / -type f -print0 | xargs -r0 -P2 grep -Hi 'the brown dog'
パフォーマンスが大幅に向上する可能性があります。ただし、2番目のgrep
は、最初のgrep
コマンドを満たすのに十分なファイルが見つかったときにのみ開始されることに注意してください。 xargs
に-n
オプションを追加して、より早く実行することができます(およびgrep
呼び出しごとに渡されるファイルが少なくなります)。
また、xargs
出力を端末デバイス以外にリダイレクトしている場合、greps
sは出力のバッファリングを開始します。つまり、これらのgrep
sの出力はおそらく誤ってインターリーブされる。それらを回避するには、stdbuf -oL
(GNUまたはFreeBSDで利用可能な場合)を使用する必要があります(非常に長い行(通常> 4KiB)で問題が発生する可能性があります) )または、それぞれの出力を個別のファイルに書き込み、最後にそれらすべてを連結します。
ここで、探している文字列は(正規表現ではなく)固定されているため、-F
オプションを使用すると違いが生じる可能性があります(grep
実装がすでに最適化する方法を知っているため)。
大きな違いをもたらす可能性があるもう1つのことは、マルチバイトロケールの場合にロケールをCに修正することです。
find / -type f -print0 | LC_ALL=C xargs -r0 -P2 grep -Hi 'the brown dog'
/proc
、/sys
...の中を見ないようにするには、-xdev
を使用して、検索するファイルシステムを指定します。
LC_ALL=C find / /home -xdev -type f -exec grep -i 'the brown dog' /dev/null {} +
または、明示的に除外するパスを削除します。
LC_ALL=C find / \( -path /dev -o -path /proc -o -path /sys \) -Prune -o \
-type f -exec grep -i 'the brown dog' /dev/null {} +
grep
呼び出しの*
が重要でない場合は、grep
のインスタンスが1つだけ開始され、フォークが空いていないため、最初のほうが効率的です。ほとんどの場合、*
を使用しても高速になりますが、エッジの場合、並べ替えによって逆になる可能性があります。
他のfind
-grep
構造が他にもある可能性があります。これらの構造は、多くの小さなファイルで特によく機能します。大量のファイルエントリとiノードを一度に読み取ると、メディアの回転でパフォーマンスが向上する場合があります。
しかし、syscall統計を見てみましょう。
見つける
> strace -cf find . -type f -exec grep -i -r 'the brown dog' {} \;
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
97.86 0.883000 3619 244 wait4
0.53 0.004809 1 9318 4658 open
0.46 0.004165 1 6875 mmap
0.28 0.002555 3 977 732 execve
0.19 0.001677 2 980 735 stat
0.15 0.001366 1 1966 mprotect
0.09 0.000837 0 1820 read
0.09 0.000784 0 5647 close
0.07 0.000604 0 5215 fstat
0.06 0.000537 1 493 munmap
0.05 0.000465 2 244 clone
0.04 0.000356 1 245 245 access
0.03 0.000287 2 134 newfstatat
0.03 0.000235 1 312 openat
0.02 0.000193 0 743 brk
0.01 0.000082 0 245 Arch_prctl
0.01 0.000050 0 134 getdents
0.00 0.000045 0 245 futex
0.00 0.000041 0 491 rt_sigaction
0.00 0.000041 0 246 getrlimit
0.00 0.000040 0 489 244 ioctl
0.00 0.000038 0 591 fcntl
0.00 0.000028 0 204 188 lseek
0.00 0.000024 0 489 set_robust_list
0.00 0.000013 0 245 rt_sigprocmask
0.00 0.000012 0 245 set_tid_address
0.00 0.000000 0 1 uname
0.00 0.000000 0 245 fchdir
0.00 0.000000 0 2 1 statfs
------ ----------- ----------- --------- --------- ----------------
100.00 0.902284 39085 6803 total
grepのみ
> strace -cf grep -r -i 'the brown dog' .
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
40.00 0.000304 2 134 getdents
31.71 0.000241 0 533 read
18.82 0.000143 0 319 6 openat
4.08 0.000031 4 8 mprotect
3.29 0.000025 0 199 193 lseek
2.11 0.000016 0 401 close
0.00 0.000000 0 38 19 open
0.00 0.000000 0 6 3 stat
0.00 0.000000 0 333 fstat
0.00 0.000000 0 32 mmap
0.00 0.000000 0 4 munmap
0.00 0.000000 0 6 brk
0.00 0.000000 0 2 rt_sigaction
0.00 0.000000 0 1 rt_sigprocmask
0.00 0.000000 0 245 244 ioctl
0.00 0.000000 0 1 1 access
0.00 0.000000 0 1 execve
0.00 0.000000 0 471 fcntl
0.00 0.000000 0 1 getrlimit
0.00 0.000000 0 1 Arch_prctl
0.00 0.000000 0 1 futex
0.00 0.000000 0 1 set_tid_address
0.00 0.000000 0 132 newfstatat
0.00 0.000000 0 1 set_robust_list
------ ----------- ----------- --------- --------- ----------------
100.00 0.000760 2871 466 total
SSDを使用していてシーク時間がごくわずかな場合は、GNU parallelを使用できます。
find /path -type f | parallel --gnu --workdir "$PWD" -j 8 '
grep -i -r 'the brown dog' {}
'
これにより、find
の検出結果に基づいて、最大8つのgrepプロセスが同時に実行されます。
これはハードディスクドライブをスラッシュしますが、SSDはそれでかなりうまく対処するはずです。