*.txt
1つのディレクトリに何百ものファイルがあるとします。最初の3つのファイルを見つけて、*.txt
検索プロセスを終了したいと思います。
このユーティリティを使用してどのようにこれを達成できますかfind
?マニュアルページを少し見てみましたが、オプションは表示されませんでした。
答え1
find
出力をパイプできますhead
。
find . -name '*.txt' | head -n 3
答え2
この他の答えいくつかの欠陥があります。コマンドは
find . -name '*.txt' | head -n 3
それでは説明があります。コメントの1つに[強調表示]:
head
パイプの左から始まり、入力を待ちます。次に、find
指定された基準に一致するファイルを開始して検索し、パイプを介して出力を送信します。head
要求された行数だけ受信して印刷すると、パイプは閉じて終了します。find
閉じたパイプを確認し、パイプも終了します。シンプルでエレガント、効率的な。
これはほぼ本当。
問題は、find
閉じたパイプが書き込みを試みたときにのみ認識されることです。この場合、4番目の一致が見つかったときです。しかし、4次戦がなければfind
進行されます。あなたの殻が待っているでしょう!スクリプトでこれが発生した場合、パイプ出力が最終的で何も追加できないことを既に知っていても、スクリプトは待つでしょう。効率が低い。
find
この特定の操作自体はすばやく完了しますが、大きなファイルツリーで複雑な検索を実行している場合は、次の操作を不必要に遅らせることで、このコマンドの効果を無視することができます。
あまり完璧ではない解決策は、以下を実行することです。
( find … & ) | head -n 3
この方法で終了すると、head
シェルはすぐに続行されます。バックグラウンドfind
プロセスは無視(または近いうちに終了)したり、pkill
他の方法でターゲットを指定したりできます。
概念を証明するために検索できます/
。私たちは1回の試合しか期待していませんが、あちこちを調べfind
ているので、時間がかかります。
find / -name / 2>/dev/null | head -n 1
問題が見つかったら、すぐにCtrl+を使用して終了します。C今比較してみてください:
pidof find ; ( find / -name / 2>/dev/null & ) | head -n 1 ; pidof find
より良い解決策は次のとおりです。
yes | head -n 2 \
| find … -print -exec sh -c '
read dummy || kill -s PIPE "$PPID"
' find-sh \;
メモ:
ここでは、一致する3つのファイルが必要ですが
head -n 2
(ではないhead -n 3
)を使用します。 3番目の一致ファイルの後、read
標準入力で入力が見つからずkill
終了しますfind
。を使用すると、4番目のファイルの後に実行されますhead -n 3
。kill
信号はです
SIGPIPE
。kill -s INT …
また、動作する必要があります。最も簡単な解決策()でSIGPIPE
終わる信号なので、特に選びました。find
find … | head -n 3
3つのファイルが必要な場合は、一致するファイルごとに1つずつ実行することは
sh
無視できます。覚えておいてください。目標は、find
バックグラウンドで無駄に実行されている状況(私が「完璧ではないソリューション」と呼ぶもの)を避けることです。オペレーティングシステムの全体的なパフォーマンスにおいて、寿命が短いいくつかのシェルよりも重要なことはありません。 「使用されなくなりました。」find
ファイルシステムを参照することをお勧めします。しかし、(最大)1000個のファイルが必要で、早くfind
ファイルが不足する可能性がある場合(それで私たちはおそらく何の問題も避けたくありません)、これらのシェルは責任があります。次のコードはプロセス数を減らしました
sh
が、欠陥があると思います。# flawed, DO NOT USE yes | head -n 999 \ | find … -exec sh -c ' for pathname do printf "%s\\n" "$pathname" read dummy || { kill -s PIPE "$PPID"; exit 0; } done ' find-sh {} +
-print
(シェルコードの外側から)を(シェルコードの内側から)に置き換える必要がありますprintf …
。その理由は、あまりにも多くのパス名が-print
前に-exec sh … {} +
印刷される可能性があるためです。潜在的な問題が発生します。誰もが
printf
別々のプロセスを作成すると、この「最適化」は意味がなくなります。幸いにも、ほぼ(?)すべてsh
printf
内蔵されています。しかし、本当の欠点は、
exec sh … {} +
パス名を渡す前にできるだけ多くのパス名を待つことですsh
。一方で、これはsh
プロセスの数を減らすことです。一方、1000番目の一致がキューに追加された場合は、find
1001番目の項目の検索が続行され、1001番目の一致が見つかった場合は、より多くの項目を検索できることがほぼ確実です。この場合、1001番目の一致が終了するため、find … | head -n 1000
この欠陥のあるソリューションは最も単純なソリューションよりも悪いので使用しないでください。find … | head -n 3
最も簡単な解決策()は、印刷されたパス名の1つに改行文字が含まれていると誤って計算されます。 nullで終わる文字列が必要な場合、最も簡単な解決策は次のとおりです。つまり、この移植不可能なオプションをサポートするfind … -print0 | head -z -n 3
必要があります。私たちの最適化されたソリューションでは、シェルコードではどちらも必要ありません。head
-z
head -z
find -print0
printf "%s\\0" "$pathname"
計算は
sh
stdinに継承された行を使用して内部的に行われますfind
。一般的には何もパイプしませんが、find
通常は計算以外の目的でパイプできます。その後、他の目的は私たちの計算方法と互換性がありません。yes
持ち運びが簡単ではありません。私たちの目的while :; do echo; done
はポータブル代替品です。find-sh
説明は次のとおりです。の2番目のshは何ですかsh -c 'some shell code' sh
?
ユーザーがこのソリューションを実装するシェル機能を要求しました。ここにいる:
findn () (
n="$1"
shift
case "$n" in
'' | *[!0123456789]*) echo >&2 not a valid number;
exit 1;;
esac
[ "$n" -eq 0 ] && exit 0
n="$((n-1))"
while :; do echo; done | head -n "$n" \
| find "$@" -exec sh -c '
read dummy || kill -s PIPE "$PPID"
' find-sh \;
)
最初のパラメータは必要な最大一致数で、残りは処理されますfind
。メモ:
その理由
case
は次のとおりです。シェル算術評価における整理されていないデータ使用のセキュリティ影響。実行時に
find
関数が追加されるため、-exec …
暗黙的な-print
。結果を印刷するには、-print
明示的に指定します。
使用例:
findn 2 / -name bin -print 2>/dev/null
答え3
find
多くの人に効果がある可能性があるこれがない解決策は、fd
Rustで書かれた検索などのツールを使用することです。 (fdはシンプルで高速でユーザーフレンドリーな選択肢です。)
fd --glob '*.txt' /path/to/search --max-results $n
答え4
4.4+ および GNU ツールを使用してbash
3 番目のファイルを見つけた後、早く終了するには、次のようにします。
n=3
readarray -td '' first_3_files < <(
(
echo "$BASHPID"
LC_ALL=C exec stdbuf -o0 find . -name '*.txt' -type f -print0
) | {
IFS= read -r pid
head -zn "$n"
kill -s PIPE "$pid"
}
)
echo "The first $n files are:"
printf ' - %s\n' "${first_3_files[@]}"
stdbuf -o0
find
検索を続行し、4番目のファイルパスを見つけて印刷するときにのみSIGPIPEを受け取るのではなく、出力バッファリングを停止して返すとすぐにSIGPIPE信号を送信します。find
head -zn 3
find
または、GNU述語を使用する別のGNU固有のfind
方法-quit
:
n=3
readarray -td '' first_3_files < <(
seq "$((n - 1))" | LC_ALL=C find . -name '*.txt' -type f -print0 \
! -exec read iteration ';' -quit)
(システムにスタンドアロンユーティリティがない場合は、read
このユーティリティを使用してください-exec sh -c 'read iteration' ';'
。read
システムは、組み込みプログラムの周りのシェルスクリプトラッパーとして実装されている可能性がありますread
。)
を使用すると、zsh
次のことができます。
first_3_files=( **/*.txt(ND.Y3) )