自分の構文を常に調べている
find . -name "FILENAME" -exec rm {} \;
主に、私は-exec
一部は機能します。ブレース、バックスラッシュ、セミコロンの意味は何ですか?その構文の他のユースケースはありますか?
この答えは次の部分にあります。
-exec
の基本的な使用法-exec
をsh -c
と組み合わせて使用する-exec ... {} +
の使用-execdir
の使用-exec
の基本的な使用法-exec
オプションは、オプションの引数を持つ外部ユーティリティを引数として取り、それを実行します。
文字列{}
が指定されたコマンドのどこかに存在する場合、その各インスタンスは、現在処理されているパス名で置き換えられます(例:./some/path/FILENAME
)。ほとんどのシェルでは、2つの文字{}
を引用符で囲む必要はありません。
コマンドは、find
が終了する場所を知るために;
で終了する必要があります(後でさらにオプションがある場合があるため)。シェルから;
を保護するには、\;
または';'
として引用する必要があります。そうしないと、シェルはfind
コマンドの終わりと見なします。
例(最初の2行の終わりにある\
は、行を継続するためのものです):
find . -type f -name '*.txt' \
-exec grep -q 'hello' {} ';' \
-exec cat {} ';'
これにより、名前がパターン-type f
と一致する通常のファイル(*.txt
)が、現在のディレクトリ以下のすべてに見つかります。次に、grep -q
を使用して、見つかったファイルのいずれかで文字列hello
が発生するかどうかをテストします(出力を生成せず、終了ステータスのみ)。文字列を含むファイルの場合、cat
が実行され、ファイルの内容が端末に出力されます。
各-exec
も、-type
および-name
と同様に、find
によって検出されたパス名に対する「テスト」のように機能します。コマンドがゼロの終了ステータス(「成功」を意味する)を返す場合、find
コマンドの次の部分が考慮されます。それ以外の場合、find
コマンドは次のパス名で続行します。これは、上記の例で文字列hello
を含むファイルを検索するために使用されますが、他のすべてのファイルは無視します。
上記の例は、-exec
の最も一般的な2つの使用例を示しています。
find
コマンドの最後に)(必須ではありません)。-exec
をsh -c
と組み合わせて使用する-exec
が実行できるコマンドは、オプションの引数を持つ外部ユーティリティに制限されています。ビルトインシェル、関数、条件、パイプライン、リダイレクトなどを-exec
で直接使用することは、sh -c
子シェルのようなものでラップしない限り不可能です。
bash
機能が必要な場合は、bash -c
の代わりにsh -c
を使用してください。
sh -c
は、コマンドラインで指定されたスクリプトを使用して/bin/sh
を実行し、その後にそのスクリプトへのオプションのコマンドライン引数を続けます。
find
なしでsh -c
を単独で使用する簡単な例:
sh -c 'echo "You gave me $1, thanks!"' sh "apples"
これにより、子シェルスクリプトに2つの引数が渡されます。これらは、スクリプトが使用するために$0
および$1
に配置されます。
文字列sh
。これは、スクリプト内で$0
として使用できます。内部シェルがエラーメッセージを出力した場合は、この文字列をプレフィックスとして付けます。
引数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" {} ';'
内部スクリプト内では、$1
は文字列text
、$2
は文字列txt
、$3
はパス名find
が見つかりました。パラメータ拡張${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
は、外部スクリプトの同じ名前の変数とは異なることに注意してください。
上記は、find
を使用して-exec
から任意の複雑なスクリプトを呼び出す正しい方法です。のようなループでfind
を使用する
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
は可能な限り数回実行されます。この最後の例では、coreutilsからGNU mv
(-t
オプションをサポート)が必要です)が必要です。
-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
はそれを行いません)。
例:
find . -type f -name '*.txt' \
-execdir mv {} done-texts/{}.done \;
これにより、見つかった各*.txt
ファイルを、ファイルが見つかった場所と同じディレクトリにある既存のdone-texts
サブディレクトリに移動します。また、サフィックス.done
を追加することにより、ファイルの名前が変更されます。
ファイルの新しい名前を形成するために-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 {} +