質問

質問

この質問は以下からインスピレーションを得ました。

シェルループを使用してテキストを処理するのはなぜ悪い習慣と見なされますか?

このような構造が見えます。

for file in `find . -type f -name ...`; do smth with ${file}; done

そして

for dir in $(find . -type d -name ...); do smth with ${dir}; done

ほぼ毎日ここで使用されます。何人かの人々はこの種のものを避けるべき理由を説明するこの記事に時間を費やしていますが、
このような投稿の数を見ると(時にはこれらのコメントが単に無視されるという事実もあります)、質問をしたいと思います。

findループの出力が悪い習慣であるのはなぜですか?返された各ファイル名/パスに対して1つ以上のコマンドを実行する正しい方法は何ですかfind

ベストアンサー1

出力反復がfind悪い習慣なのはなぜですか?

簡単な答えは次のとおりです。

ファイル名には以下を含めることができます。どの特徴。

だから、ファイル名を区別するために確実に使用できる印刷可能文字はありません。


改行文字は次のとおりです。よく(不正確に)ファイル名を区別する理由は次のとおりです。以上ファイル名に改行を含めます。

ただし、任意の仮定に基づいてソフトウェアを構築すると、せいぜい例外を処理できなくなり、最悪の場合、システム制御を失う悪意のある攻撃に対して脆弱になります。したがって、これは堅牢性とセキュリティの問題です。

2つの異なる方法でソフトウェアを書くことができますが、そのうちの1つは極端なケース(異常な入力)を正しく処理しましたが、もう1つは読みやすくなった場合はこれをトレードオフとして考えることができます。 (そうではありません。私は正しいコードを好みます。)

しかし、正確で強力なコードバージョンがある場合返品読みやすく極端な場合に失敗するコードを書く理由はありません。find見つかったすべてのファイルに対してコマンドを実行する必要がある場合です。


より具体的に説明すると、UNIXまたはLinuxシステムでは、ファイル名に/a(パスコンポーネント区切り文字として使用)を除く任意の文字を含めることができ、nullバイトを含めることはできません。

したがって、ヌルバイトはただファイル名を分離する正しい方法。


GNUにはメインがfind含まれているため、-print0印刷するファイル名を区切るためにNULLバイトを使用します。 GNUfind できるxargs次の出力を処理する-0ために、GNUとそのフラグ(およびフラグ)と一緒に安全に使用できます。-rfind

find ... -print0 | xargs -r0 ...

しかし、正しい方法はありません。理由次の理由でこのフォームを使用してください。

  1. 存在する必要のないGNU findutilsへの依存関係を追加します。
  2. findはいデザイン済み見つかったファイルに対してコマンドを実行する機能。

また、GNUではxargs必須で-0あり、-rFreeBSDではxargs必須のみが必要であり-0(オプションはありません-r)、一部ではxargsまったくサポートされていません。したがって、POSIX機能(次のセクションを参照)に固執してスキップすることをお勧めします-0findxargs

findポイント2(見つかったファイルに対してコマンドを実行する機能)については、Mike Loukidesが最もよく言ったようです。

find仕事はファイルを見つけるのではなく、式を評価することです。はい、findもちろんファイルを見つけることができますが、実際には副作用だけです。

- Unix電動工具


POSIX 指定目的find

find各結果に対して1つ以上のコマンドを実行する正しい方法は何ですか?

見つかった各ファイルに対して単一のコマンドを実行するには、次のようにします。

find dirname ... -exec somecommand {} \;

見つかった各ファイルに対して複数のコマンドを順番に実行するには、最初のコマンドが成功した場合にのみ2番目のコマンドを実行するには、次のようにします。

find dirname ... -exec somecommand {} \; -exec someothercommand {} \;

複数のファイルに対して単一のコマンドを同時に実行するには、次のようにします。

find dirname ... -exec somecommand {} +

find結合するsh

使用する必要がある場合シェル出力リダイレクト、ファイル名から拡張子の削除など、コマンド内の機能にこのsh -c構成を使用できます。これについて知っておくべきことがいくつかあります。

  • いいえ{}shコードに直接挿入してください。これにより、悪意を持って作成されたファイル名から任意のコードを実行する可能性があります。そして、実際にはPOSIXは動作することを明示していません。 (次のトピックを参照してください。)

  • {}何度も使用したり、長い引数の一部として使用しないでください。 これはポータブルではありません。たとえば、次のようにしないでください。

    find ... -exec cp {} somedir/{}.bak \;

    引用するPOSIX仕様find:

    もしユーティリティ名またはディスカッション文字列に「{}」の2文字が含まれているかどうかは実装によって定義されますが、「{}」の2文字のみが含まれるわけではありません。探すこれら2つの文字を置き換えるか、変更されていない文字列を使用してください。

    ...2 文字 "{}" を含む引数が複数ある場合、動作は指定されません。

  • オプションに渡されるシェルコマンド文字列の後の引数は、-cシェルの位置引数に設定されます。から始まる$0。で始めないでください$1

    したがって、たとえば、生成されたシェルからエラーを報告するために使用される「ダミー」$0値を含めることをお勧めします。これにより、複数のファイルをシェルに渡すのとfind-sh同じ構文を使用できます。一方、省略された値は、最初に渡されたファイルがに設定されているため、含まれていないことを意味します。"$@"$0$0"$@"


各ファイルに対して単一のシェルコマンドを実行するには、次のようにします。

find dirname ... -exec sh -c 'somecommandwith "$1"' find-sh {} \;

ただし、通常、見つかったすべてのファイルに対してシェルを生成しないように、シェルループでファイルを処理する方が良いパフォーマンスが得られます。

find dirname ... -exec sh -c 'for f do somecommandwith "$f"; done' find-sh {} +

(各位置引数はfor f do同じfor f in "$@"; doで、順番に処理されます。つまり、find名前に特殊文字があるかどうかに関係なく、見つかったすべてのファイルを使用します。)


正しい使い方findの追加例:

(注:このリストを自由に展開してください。)

おすすめ記事