「find」の-execオプションについて

「find」の-execオプションについて

私は常に構文を探していることを発見します。

find . -name "FILENAME"  -exec rm {} \;

-exec主にその部分が正確にどのように機能するのか理解していないからです。中かっこ、バックスラッシュ、セミコロンはどういう意味ですか?この構文の他のユースケースはありますか?

ベストアンサー1

この回答は次の部分に分けられます。

  • 基本的な使い方-exec
  • -execとともにsh -c
  • 使用-exec ... {} +
  • 使用-execdir

基本的な使い方-exec

この-execオプションは、オプションのパラメータを持つ外部ユーティリティを引数として使用して実行します。

この文字列が{}指定されたコマンドのどこにでも表示されると、この文字列の各インスタンスは現在処理中のパス名(たとえば./some/path/FILENAME)に置き換えられます。ほとんどのシェルでは、これら2つの文字を{}引用符で囲む必要はありません。

コマンドが終わる場所を知るには、コマンドがforで;終わる必要があります(その後にさらに多くのオプションがある可能性があるため)。シェルからfind保護するには、orで引用する必要があります。そうでなければ、シェルはそれをコマンドの終わりとして扱います。;\;';'find

はい(\最初の2行の終わりに連続専用行):

find . -type f -name '*.txt'      \
   -exec grep -q 'hello' {} ';'   \
   -exec cat {} ';'

これにより、現在のディレクトリ内または下のパターンと-type f名前が一致するすべての一般ファイル()が検索されます。次に、その文字列が見つかったファイルに表示されるかどうかを*.txtテストします(出力はなく、終了状態のみを生成します)。この文字列を含むファイルの場合、ファイルの内容は端末に出力されます。hellogrep -qcat

それぞれは、と同様に、-exec見つかったパス名の「テスト」に似ています。コマンドが終了ステータス0(「成功」を示す)を返すと、コマンドの次の部分が考慮され、そうでない場合、コマンドは次のパス名に続きます。これは上記の例では文字列を含むファイルを見つけるために使用されていますが、他のすべてのファイルは無視されます。find-type-namefindfindhello

上記の例は、最も一般的な2つのユースケースを示しています-exec

  1. 検索をさらに制限するためのテストです。
  2. 見つかったパス名に対していくつかの操作を実行します(通常はコマンドの最後で必ずしもそうではありませんfind)。

-execとともにsh -c

実行できるコマンドは、オプションのパラメーターを持つ-exec外部ユーティリティーに制限されます。シェル組み込み、関数、条件文、パイプ、リダイレクトなどを直接使用することは、サブシェルのようなもので-execラップされない限り不可能です。sh -c

bash機能が必要な場合は、bash -c代わりにを使用してくださいsh -c

sh -c/bin/shコマンドラインで提供されたスクリプトを使用して実行し、スクリプトのオプションのコマンドライン引数を使用します。

sh -c以下の方法で単独で使用される簡単な例find

sh -c 'echo  "You gave me $1, thanks!"' sh "apples"

これはサブシェルスクリプトに2つのパラメータを渡します。これはスクリプトに配置され使用さ$0れます。$1

  1. ひもsh。これはスクリプト内で使用でき$0、内部シェルがエラーメッセージを出力する場合は、この文字列をプレフィックスとして使用します。

  2. このパラメータはスクリプトでapples使用でき$1、より多くのパラメータがある場合$2などに使用できます。$3リストでも使用できます(リストに含まれていない項目を"$@"除く)。$0"$@"

これは、.と共に使用すると、-exec見つかったパス名に対して機能する任意の複雑なスクリプトを作成できるため、非常に便利ですfind

例:特定のファイル名サフィックスを持つすべての一般的なファイルを見つけて、そのファイル名サフィックスを別のサフィックスに変更します。ここで、サフィックスは変数に格納されます。

from=text  #  Find files that have names like something.text
to=txt     #  Change the .text suffix to .txt

find . -type f -name "*.$from" -exec sh -c 'mv "$3" "${3%.$1}.$2"' sh "$from" "$to" {} ';'

内部スクリプトでは、$1string text$2string txt、そして私たちが見つけたすべてのパス名になります$3findパラメータ拡張はパス名を取得し、サフィックスを${3%.$1}削除します。.text

またはdirname/を使用してくださいbasename

find . -type f -name "*.$from" -exec sh -c '
    mv "$3" "$(dirname "$3")/$(basename "$3" ".$1").$2"' sh "$from" "$to" {} ';'

または、内部スクリプトに変数を追加します。

find . -type f -name "*.$from" -exec sh -c '
    from=$1; to=$2; pathname=$3
    mv "$pathname" "$(dirname "$pathname")/$(basename "$pathname" ".$from").$to"' sh "$from" "$to" {} ';'

最後のバリエーションでは、サブシェルの変数は外部スクリプトの同じ名前を持つ変数fromとは異なります。to

-exec上記は、から複雑なスクリプトを呼び出す正しい方法ですfindfindたとえば、ループに使用されます。

for pathname in $( find ... ); do

エラーが発生しやすくエレガントではありません(個人的なコメント)。ファイル名をスペースに分割し、ファイル名ワイルドカードを呼び出し、findループの最初の反復を実行する前に、シェルが結果全体を拡張するように強制します。

また見なさい:


使用-exec ... {} +

;最後のものはに置き換えることができます+。これにより、find見つかった各パス名に対して1回ではなく、できるだけ多くの引数(見つかったパス名)を使用して指定されたコマンドが実行されます。 正しく機能するには、文字列が{} 最初に表示される必要があります。+

find . -type f -name '*.txt' \
   -exec grep -q 'hello' {} ';' \
   -exec cat {} +

ここではfind生成されたパス名を集め、できるだけ多くのcatパスを一度に実行する。

find . -type f -name "*.txt" \
   -exec grep -q "hello" {} ';' \
   -exec mv -t /tmp/files_with_hello/ {} +

繰り返しますが、これはmvできるだけ少ない回数で実行されます。最後の例mvでは、coreutils(このオプションをサポートしています-t)にGNUが必要です。

を使用することは、-exec sh -c ... {} +任意の複雑なスクリプトを介して一連のパス名を繰り返す効率的な方法です。

基本は使用するのと同じです-exec sh -c ... {} ';'が、スクリプトには長いパラメータのリストが必要です。"$@"スクリプト内を繰り返して繰り返すことができます。

前のセクションでファイル名のサフィックスを変更する例:

from=text  #  Find files that have names like something.text
to=txt     #  Change the .text suffix to .txt

find . -type f -name "*.$from" -exec sh -c '
    from=$1; to=$2
    shift 2  # remove the first two arguments from the list
             # because in this case these are *not* pathnames
             # given to us by find
    for pathname do  # or:  for pathname in "$@"; do
        mv "$pathname" "${pathname%.$from}.$to"
    done' sh "$from" "$to" {} +

使用-execdir

また-execdir(ほとんどのバリエーションで実装されていますが、find標準オプションではありません)

これは-exec、見つかったパス名のディレクトリを現在の作業ディレクトリとして使用して実行されている特定のシェルコマンドと同様に機能し、パスなしで見つかったパス名{}のデフォルト名を含みますが、GNUはfindまだデフォルト名の前にプレフィックスを付けます./。 BSDはfindそうsfindではありません。

例:

find . -type f -name '*.txt' \
    -execdir mv -- {} 'done-texts/{}.done' \;

これにより、見つかった各*.txtファイルが既存のdone-textsサブディレクトリに移動されます。ファイルが見つかったディレクトリと同じディレクトリ.doneサフィックスを追加すると、ファイル名も変更されます。 、デフォルト名の前に。を付けない実装では、--オプションの終わりを表示する必要があります。シェルが含まれている場合は、完全に含まれていない引数を囲む必要があります。また、すべての実装がそこに拡張されるわけではありません(そうではありません)。find./{}(t)cshfind{}sfind

ファイルの新しい名前を設定する-execには、見つかったファイルのデフォルト名を取得する必要があるため、これは少しトリッキーです。また、ディレクトリを正しく見つける{}には、ディレクトリ名が必要です。{}done-texts

それで-execdir何か簡単になります。

-exec逆の対応するアクションは-execdirサブシェルを使用する必要があります。

find . -type f -name '*.txt' -exec sh -c '
    for name do
        mv "$name" "$( dirname "$name" )/done-texts/$( basename "$name" ).done"
    done' sh {} +

または、

find . -type f -name '*.txt' -exec sh -c '
    for name do
        mv "$name" "${name%/*}/done-texts/${name##*/}.done"
    done' sh {} +

おすすめ記事