find を使用して最初の一致ファイルのみを検索します。

find を使用して最初の一致ファイルのみを検索します。

*.txt1つのディレクトリに何百ものファイルがあるとします。最初の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 3kill

  • 信号はですSIGPIPEkill -s INT …また、動作する必要があります。最も簡単な解決策()でSIGPIPE終わる信号なので、特に選びました。findfind … | 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番目の一致がキューに追加された場合は、find1001番目の項目の検索が続行され、1001番目の一致が見つかった場合は、より多くの項目を検索できることがほぼ確実です。この場合、1001番目の一致が終了するため、find … | head -n 1000この欠陥のあるソリューションは最も単純なソリューションよりも悪いので使用しないでください。

  • find … | head -n 3最も簡単な解決策()は、印刷されたパス名の1つに改行文字が含まれていると誤って計算されます。 nullで終わる文字列が必要な場合、最も簡単な解決策は次のとおりです。つまり、この移植不可能なオプションをサポートするfind … -print0 | head -z -n 3必要があります。私たちの最適化されたソリューションでは、シェルコードではどちらも必要ありません。head-zhead -zfind -print0printf "%s\\0" "$pathname"

  • 計算はshstdinに継承された行を使用して内部的に行われます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。メモ:

使用例:

findn 2 / -name bin -print 2>/dev/null

答え3

find多くの人に効果がある可能性があるこれがない解決策は、fdRustで書かれた検索などのツールを使用することです。 (fdはシンプルで高速でユーザーフレンドリーな選択肢です。)

fd --glob '*.txt' /path/to/search --max-results $n

答え4

4.4+ および GNU ツールを使用してbash3 番目のファイルを見つけた後、早く終了するには、次のようにします。

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 -o0find検索を続行し、4番目のファイルパスを見つけて印刷するときにのみSIGPIPEを受け取るのではなく、出力バッファリングを停止して返すとすぐにSIGPIPE信号を送信します。findhead -zn 3find

または、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) )

関連情報