
ファイルを繰り返すには2つの方法があります。
-ループの使用
for
:for f in *; do echo "$f" done
使用
find
:find * -prune | while read f; do echo "$f" done
2つのループが同じファイルのリストを見つけると仮定すると、2つのオプションの違いは何ですかパフォーマンスそして処理?
答え1
私は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
(for i in ...)がより速いことを示しています。find
bash
完璧にするために、find
あなたの例ではパイプが完全に重複しているので、パイプを削除しました。ただ出力は次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/null
収率:
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
結論として:
- これまでに最も遅い方法です。このアプローチを提案する回答の説明で指摘したように、各引数に対してシェルが生成されます。
答え2
1.
最初:
for f in *; do echo "$f" done
-n
-e
ファイル名にバックスラッシュが含まれる一部のbashディストリビューションでは、名前とバリアントのファイルに対して-nene
失敗します。
第二:
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
。
三。
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/8kB)より大きい場合。この場合、ループ内処理はfind
すべてのファイル検索が完了する前に開始されます。 GNUおよびFreeBSDシステムでは、stdbuf
これを使用してより速く実行できます(stdioバッファリングを無効にする)。
5.
各ファイルに対してコマンドを実行するPOSIX /標準/移植可能な方法は、述語をfind
使用することです。-exec
find . ! -name . -prune ! -name '.*' -exec some-cmd {} ';'
ただし、この場合、シェルで繰り返すよりも効率が低くなります。シェルには、新しいプロセスを作成し、各ファイルに対して実行する必要があるwhileecho
の組み込みバージョンがあるためです。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の短い名前の場合、最初のzsh
項目(セクション2)は最も速く、最初の単純なfor i in *
ループはそれに従います(通常はbash
他のシェルよりはるかに遅いです)。
答え3
私は間違いなく検索を選択しますが、検索を次のように変更します。
find . -maxdepth 1 -exec echo {} \;
もちろん、パフォーマンスの面ではfind
必要に応じてはるかに高速です。現在持っているのは、for
ディレクトリの内容ではなく、現在のディレクトリのファイル/ディレクトリだけを表示します。 find を使用すると、サブディレクトリの内容も表示されます。
for
まず拡張しなければならないので find がより良いと言って*
、ファイルが多いディレクトリがあるとエラーが発生するか心配されます。パラメータリストが長すぎます。。以下にも適用されます。find *
たとえば、現在作業中のシステムには、200万を超えるファイル(それぞれ10万未満)を含む複数のディレクトリがあります。
find *
-bash: /usr/bin/find: Argument list too long
答え4
しかし、私たちはパフォーマンスの問題にこだわっています!実験に対するこの要求は、妥当性を低下させる少なくとも2つの仮定をします。
A. 同じファイルが見つかったとします。
まあ、彼ら〜する同じファイルがすべて同じglobを繰り返すので、最初に見つかります。つまり、*
いくつfind * -prune | while read f
かの欠陥があり、予想されるすべてのファイルが見つからない可能性があります。
- POSIX findは複数のパスパラメータを受け入れることは保証されていません。ほとんどの
find
実装ではこれを行いますが、それに依存してはいけません。 find *
ぶつかると壊れますARG_MAX
。for f in *
いいえ。組み込み機能ではなくARG_MAX
に適用されるからです。exec
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