forとbashの検索


28

ファイルをループする場合、2つの方法があります。

  1. for-loopを使用します。

    for f in *; do
        echo "$f"
    done
  2. 使用find

    find * -prune | while read f; do 
        echo "$f"
    done

これらの2つのループが同じファイルのリストを見つけると仮定すると、パフォーマンスと処理におけるこれら2つのオプションの違いは何ですか?


1
どうして?find見つかったファイルを開きません。多数のファイルに関してここで噛みついているのはARG_MAXだけです。
小次郎

1
read fファイル名を読み取るときにファイル名を破壊することを示す回答とコメントを参照してください(たとえば、先頭に空白のある名前)。またfind * -prune、単にls -1「はい」と言うのは非常に複雑な方法のようです。
イアンD.アレン

4
2つのループが同じファイルセットを見つけると想定しないでください。ほとんどの場合、彼らはしません。また、である必要がfind .ありfind *ます。
アレクシス

1
@terdonはい、解析ls -lは悪い考えです。しかし、解析ls -1(それはaで1はないl)は解析よりも悪くはありませんfind * -prune。両方とも、名前に改行があるファイルでは失敗します。
イアンD.アレン

5
私たちはそれぞれ、この質問と回答を読むことに時間を費やしているのではないかと疑っています。
mpez0

回答:


9

1。

最初の1つ:

for f in *; do
  echo "$f"
done

と呼ばれるファイル-n-eおよび-nene一部のbashデプロイメントのようなバリアントでは、バックスラッシュを含むファイル名で失敗します。

二番目:

find * -prune | while read f; do 
  echo "$f"
done

(と呼ばれるファイルにもより多くのケースで失敗した!-H-name(...開始または終了空白と改行文字を含むファイル名)

展開するのはシェルで*find引数として受け取ったファイルを印刷するだけです。あなたにも使うことができたprintf '%s\n'として、その代わりにprintf組み込みも避けるだろうですあまりにも多くの引数潜在的なエラーを。

2。

の展開*はソートされます。ソートが必要ない場合は、少し速くすることができます。でzsh

for f (*(oN)) printf '%s\n' $f

または単に:

printf '%s\n' *(oN)

bash私が知る限り、同等のものはないので、に頼る必要がありますfind

3。

find . ! -name . -prune ! -name '.*' -print0 |
  while IFS= read -rd '' f; do
    printf '%s\n' "$f"
  done

(上記のGNU / BSD -print0非標準拡張を使用)。

それでもfindコマンドを生成して低速while readループを使用するためfor、ファイルのリストが膨大でない限り、ループを使用するよりもおそらく低速になります。

4。

また、シェルワイルドカード拡張とfindは異なりlstat、各ファイルでシステムコールを実行するため、非ソートがそれを補うことはほとんどありません。

GNU / BSD findでは-maxdepth、最適化をトリガーする拡張機能を使用することで回避できますlstat

find . -maxdepth 1 ! -name '.*' -print0 |
  while IFS= read -rd '' f; do
    printf '%s\n' "$f"
  done

findファイル名が見つかったらすぐに出力を開始するため(stdio出力バッファリングを除く)、ループ内で行うことは時間がかかり、ファイル名のリストがstdioバッファ(4 / 8 kB)。その場合、ループ内の処理は、findすべてのファイルの検索が完了する前に開始されます。GNUおよびFreeBSDシステムでは、stdbufそれをより早く発生させるために使用できます(stdioバッファリングを無効にします)。

5。

各ファイルに対してコマンドを実行するPOSIX / standard / portableの方法findは、-exec述語を使用することです:

find . ! -name . -prune ! -name '.*' -exec some-cmd {} ';'

以下の場合、echoシェルは組み込みのバージョンがありますようにシェルのループを行うよりも効率のですけれども、echoしばらくfind新しいプロセスを生成して実行する必要があります/bin/echoファイルごとにそれに。

複数のコマンドを実行する必要がある場合は、次を実行できます。

find . ! -name . -prune ! -name '.*' -exec cmd1 {} ';' -exec cmd2 {} ';'

ただし、成功したcmd2場合にのみ実行されることに注意してくださいcmd1

6。

各ファイルに対して複雑なコマンドを実行する標準的な方法は、次を使用してシェルを呼び出すことです-exec ... {} +

find . ! -name . -prune ! -name '.*' -exec sh -c '
  for f do
    cmd1 "$f"
    cmd2 "$f"
  done' sh {} +

そのとき、組み込みのechoを使用しているためsh-exec +バージョンがshできるだけ少なく生成されるため、効率的になりました。

7。

では200.000のファイルとディレクトリ上の私のテスト ext4の上の短い名前を持つ、zsh1(パラグラフ2)は最初に簡単なのに続いて、これまで最速であるfor i in *ループ(いつものように、かかわらず、bash多くの低速の他のシェルよりもそのためです)。


何をん!findコマンドで行いますか?
rubo77 14

@ rubo77 !は否定用です。! -name . -prune more...行います-prune(およびmore...ので、-prune常にtrueを返します)すべてのファイルのためではなく.。そのためmore...、すべてのファイルで実行されますが.、除外.され、サブディレクトリに降りません.。したがって、これはGNUの標準的な同等物です-mindepth 1 -maxdepth 1
ステファンシャゼル14

18

2259エントリのディレクトリでこれを試し、timeコマンドを使用しました。

time for f in *; do echo "$f"; done(マイナスファイル!)の出力は次のとおりです。

real    0m0.062s
user    0m0.036s
sys     0m0.012s

time find * -prune | while read f; do echo "$f"; done(マイナスファイル!)の出力は次のとおりです。

real    0m0.131s
user    0m0.056s
sys     0m0.060s

キャッシュミスをなくすために、各コマンドを数回実行しました。これは、出力bashを使用findして(toにbash)パイプするよりも(for i in ...)に保持することをお勧めします

完全を期すfindために、あなたの例では完全に冗長なので、からパイプを落としました。justの出力find * -pruneは次のとおりです。

real    0m0.053s
user    0m0.016s
sys     0m0.024s

また、time echo *(出力は改行で区切られていません、悲しいかな):

real    0m0.009s
user    0m0.008s
sys     0m0.000s

この時点で、echo *それほど多くの改行を出力していないため、出力がそれほどスクロールしていないので、理由はもっと速いと思います。テストしてみましょう...

time find * -prune | while read f; do echo "$f"; done > /dev/null

収量:

real    0m0.109s
user    0m0.076s
sys     0m0.032s

一方、time find * -prune > /dev/null収量:

real    0m0.027s
user    0m0.008s
sys     0m0.012s

およびtime for f in *; do echo "$f"; done > /dev/null収量:

real    0m0.040s
user    0m0.036s
sys     0m0.004s

そして最後に:time echo * > /dev/nullyields:

real    0m0.011s
user    0m0.012s
sys     0m0.000s

変動の一部はランダムな要因で説明できますが、明らかなようです:

  • 出力が遅い
  • 配管には少し費用がかかります
  • for f in *; do ...find * -prune、それ自体ではより遅いですが、パイプを含む上記の構造では、より速くなります。

また、余談ですが、両方のアプローチは、スペースで問題なく名前を処理するように見えます。

編集:

find . -maxdepth 1 > /dev/null対のタイミングfind * -prune > /dev/null

time find . -maxdepth 1 > /dev/null

real    0m0.018s
user    0m0.008s
sys     0m0.008s

find * -prune > /dev/null

real    0m0.031s
user    0m0.020s
sys     0m0.008s

したがって、追加の結論:

  • find * -pruneより遅いfind . -maxdepth 1-前者では、シェルはglobを処理してから、の(大きな)コマンドラインを構築していfindます。注意:をfind . -prune返します.

その他のテスト time find . -maxdepth 1 -exec echo {} \; >/dev/null::

real    0m3.389s
user    0m0.040s
sys     0m0.412s

結論:

  • これまでで最も遅い方法。このアプローチが提案された答えに対するコメントで指摘されたように、各引数はシェルを生成します。

どのパイプが冗長ですか?パイプなしで使用したラインを表示できますか?
rubo77

2
@ rubo77 find * -prune | while read f; do echo "$f"; doneには冗長なパイプがあります-パイプが行っているのは、出力を正確にfind出力することだけです。パイプがなければ、それは単純になりますfind * -prune 。パイプの反対側にあるものがstdinをstdoutに(ほとんどの場合)コピーするだけなので、パイプは特に冗長です。高価なノーオペレーションです。findの出力を使って何かをやり直したい場合は、単に再び吐き出す以外に、それは異なります。
フィル

たぶんメインtimeconsumingです*BitsOfNixは、次のように述べた。私はまだ強くは使用しないことをお勧め*して.のためにfind代わりに。
rubo77

@ rubo77はそのようです。見落としていたと思います。システムに調査結果を追加しました。私が想定しfind . -pruneているため高速であるfindシェルも同様にやっているだろうしながら、潜在的に(最適化可能性があるグロブに対してマッチング、逐語的にディレクトリエントリを読んされます*、その後のための大規模なコマンドラインを構築し、) find
フィル

1
find . -prune.私のシステムでのみ印刷します。ほとんど動作しません。find * -prune現在のディレクトリ内のすべての名前を表示するものとはまったく異なります。裸のread f場合、ファイル名の先頭にスペースが付きます。
イアンD.アレン

10

私はあなたの検索をこれに変更しますが、間違いなく検索に行きます:

find . -maxdepth 1 -exec echo {} \;

賢明なパフォーマンスfindは、もちろんあなたのニーズに応じてはるかに高速です。現在使用しforているものは、現在のディレクトリ内のファイル/ディレクトリのみを表示し、ディレクトリの内容は表示しません。findを使用すると、サブディレクトリの内容も表示されます。

findの方が良いと言うのはfor*最初にファイルを展開する必要があるからです。大量のファイルがあるディレクトリがあると、エラー引数リストが長くなりすぎるのではないかと思います。同じことが言えますfind *

例として、私が現在使用しているシステムの1つには、200万を超えるファイル(それぞれ<100k)のあるディレクトリがいくつかあります。

find *
-bash: /usr/bin/find: Argument list too long

-prune2つの例をより似たものにするために追加しました。そして、whileのパイプを好むので、ループ内により多くのコマンドを適用するのが簡単です
rubo77


ハード制限を変更することは、POVからの適切な回避策ではありません。特に2百万個のファイルについて話すとき。質問から逸脱することなく、単純なケースでは1レベルのディレクトリの方が高速ですが、ファイル/ディレクトリ構造を変更すると、移行が難しくなります。findを使用すると、膨大な量のオプションを使用できますが、より適切に準備できます。それでも、*と。を使用しないことを強くお勧めします。代わりに検索します。それは...あなたはハード限界を制御することができない場合があります*よりポータブルになる
BitsOfNix

4
これにより、ファイルごとに1つのエコープロセスが生成され(シェルforループでは、余分なプロセスをフォークせずに使用されるエコービルトインです)、ディレクトリに下降するため、かなり遅くなります。また、ドットファイルが含まれることに注意してください。
ステファンシャゼル

そのとおりです。maxdepth1を追加して、現在のレベルのみに固定するようにしました。
BitsOfNix

7
find * -prune | while read f; do 
    echo "$f"
done

無駄な使い方ですfind-あなたが言っているのは、「ディレクトリ(*)内の各ファイルに対して、ファイルを見つけないでください」ということです。また、いくつかの理由で安全ではありません。

  • パス内のバックスラッシュは、 -rオプションれreadます。これはforループの問題ではありません。
  • パス内の改行は、ループ内の重要な機能を破壊します。これは、の問題ではありませんforループの。

でファイル名を処理するのfind難しいのでfor、その理由だけで可能な限りループオプションを使用する必要があります。また、find一般的な外部プログラムの実行は、一般的な内部ループコマンドの実行よりも遅くなりますfor


@ I0b0 find -path './*' -pruneまたはfind -path './[^.]*'はどうですか?-prune(隠しファイルとディレクトリを避けるため)をより良い構成として-完全な形式で:find -path ' ./* '-prune -print0 | xargs -0 sh -c '...'?
AsymLabs

1
どちらfind-print0xargs" -0POSIXに互換性がある、とあなたはで任意のコマンドを置くことができないsh -c ' ... '、それはそれほど単純ではないので、(単一引用符は、単一引用符でエスケープすることはできません)。
l0b0

4

しかし、私たちはパフォーマンスに関する質問に夢中です!この実験要求は、少なくとも2つの仮定を行っているため、それほど有効ではありません。

A.同じファイルを見つけたと仮定します…

まあ、彼ら最初に同じファイル見つけるでしょう、なぜなら彼らは同じグロブ、つまり*。しかしfind * -prune | while read f、期待するすべてのファイルを見つけることができない可能性があるいくつかの欠陥に苦しんでいます:

  1. POSIX findは、複数のパス引数を受け入れることが保証されていません。ほとんどのfind実装は実行しますが、それでも、それに依存するべきではありません。
  2. find *打つと壊れることがありARG_MAXます。for f in *しません、ためARG_MAXに適用されexec、組み込みコマンドではありません。
  3. while read f空白で始まるファイル名と空白で終わるファイル名で破損する可能性があります。これwhile readとそのデフォルトのパラメーターを使用してこれを克服することもできREPLYますが、改行を含むファイル名に関してはまだ役に立ちません。

B. echo。ファイルの名前をエコーするためだけにこれを行う人はいません。必要な場合は、次のいずれかを実行してください。

printf '%s\n' *
find . -mindepth 1 -maxdepth 1 # for dotted names, too

whileここのループへのパイプは、ループの終了時に閉じる暗黙のサブシェルを作成します。

質問に答えるために、184個のファイルとディレクトリが含まれる私のディレクトリの結果を以下に示します。

$ time bash -c 'for i in {0..1000}; do find * -prune | while read f; do echo "$f"; done >/dev/null; done'

real    0m7.998s
user    0m5.204s
sys 0m2.996s
$ time bash -c 'for i in {0..1000}; do for f in *; do echo "$f"; done >/dev/null; done'

real    0m2.734s
user    0m2.553s
sys 0m0.181s
$ time bash -c 'for i in {0..1000}; do printf '%s\n' * > /dev/null; done'

real    0m1.468s
user    0m1.401s
sys 0m0.067s

$ time bash -c 'for i in {0..1000}; do find . -mindepth 1 -maxdepth 1 >/dev/null; done '

real    0m1.946s
user    0m0.847s
sys 0m0.933s

以下は、貧しい人々のための謝罪は、フォーマット、前と後に表示するようにしようとしている。最悪の場合で、新しいスレッド-私は、while文のループスポーンサブシェルに同意しない$ ps ax | grep bash 20784 pts/1 Ss 0:00 -bash 20811 pts/1 R+ 0:00 grep bash $ while true; do while true; do while true; do while true; do while true; do sleep 100; done; done; done; done; done ^Z [1]+ Stopped sleep 100 $ bg [1]+ sleep 100 & $ ps ax | grep bash 20784 pts/1 Ss 0:00 -bash 20924 pts/1 S+ 0:00 grep bash
フィル

技術的に見落としがあります。パイプはwhileループではなく、暗黙的なサブシェルを引き起こします。編集します。
小次郎

2

find **パスではなく述語のように見えるトークンを生成する場合、正しく機能しません。

--これを修正するために通常の引数を使用することはできません--オプションの終わりを示しており、findのオプションはパスの前にある。

この問題を解決するには、find ./*代わりに使用できます。しかし、それでは、とまったく同じ文字列が生成されませんfor x in *

find ./* -prune | while read f ..のスキャン機能は実際には使用しないことに注意してくださいfind./*ディレクトリを実際に走査して名前を生成するのは、グロビング構文です。次に、findプログラムはstat、これらの名前のそれぞれに対して少なくともチェックを実行する必要があります。プログラムを起動してこれらのファイルにアクセスし、I / Oを実行して出力を読み取るオーバーヘッドがあります。

それがどのようになるかを想像するのは難しいですが、より効率的ではありませんfor x in ./* ...


1

まあ初心者のためforながら、バッシュに組み込まれ、シェルキーワードでfind別の実行可能ファイルです。

$ type -a for
for is a shell keyword

$ type -a find
find is /usr/bin/find

forそれが膨張するとループにのみ、それが見つかったディレクトリに再帰的ではないだろう、globstar文字からファイルを検索します。

一方、Findはglobstarによって展開されたリストを与えられますが、この展開されたリストの下にあるすべてのファイルとディレクトリを再帰的に検索し、それぞれをパイプして whileループにます。

これらのアプローチは両方とも、スペースを含むパスまたはファイル名を処理しないという意味で危険と見なされる場合があります。

これで、これらの2つのアプローチについてコメントする価値があると思います。


findコマンドに-pruneを追加したので、より似ています。
rubo77

0

findによって返されたすべてのファイルを単一のコマンドで処理できる場合(上記のエコーの例には明らかに当てはまらない)、xargsを使用できます。

find * |xargs some-command

0

何年もの間私はこれを使用しています:

find . -name 'filename'|xargs grep 'pattern'|more

画面からスクロールしないようにgrepが検索してパイプすることができるパターンを含む特定のファイル(* .txtなど)を検索します。時々>>パイプを使用して、後で確認できる別のファイルに結果を書き込みます。

結果のサンプルは次のとおりです。

./Documents/Organ_docos/Rodgerstrio321A/rodgersmylist/2008-August.txt:Message-ID: <A165CE5C-61C5-4794-8651-66F5678ABCBF@usit.net>
./Documents/Organ_docos/Rodgerstrio321A/rodgersmylist/2008-August.txt:In-Reply-To: <A165CE5C-61C5-4794-8651-66F5678ABCBF@usit.net>
./Documents/Organ_docos/Rodgerstrio321A/rodgersmylist/2008-August.txt:  <A165CE5C-61C5-4794-8651-66F5678ABCBF@usit.net>
./Documents/Organ_docos/Rodgerstrio321A/rodgersmylist/2008-August.txt:  <A165CE5C-61C5-4794-8651-66F5678ABCBF@usit.net>
./Documents/Organ_docos/Rodgerstrio321A/rodgersmylist/2008-August.txt:Message-ID: <448E53556A3F442ABC58203D6281923E@hypermax>
./Documents/Organ_docos/Rodgerstrio321A/rodgersmylist/2011-April.txt:URL: http://mylist.net/private/rodgersorganusers/attachments/20110420/3f
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.