`find`の-execオプションを理解する


53

私は常に自分自身の構文を調べています

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

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


11
@Philippos:あなたの主張がわかります。マニュアルページは参照であることに注意してください。つまり、構文を調べる問題を理解している人にとって有用です。トピックに慣れていない人にとっては、彼らはしばしば謎めいた、そしてフォーマルな人に役立つでしょう。受け入れられた回答は、manページのエントリの約10倍の長さであり、これには理由があります。
ゾルトSzilagy

6
古いPOSIX manページでも、2つの文字 "{}"のみを含むutility_nameまたは引数を読み取りますが、これは現在のパス名置き換えられ、十分なようです。さらに-exec rm {} \;、質問のように、の例があります。私の時代には、印刷されたmanページの本である「大きな灰色の壁」以外のリソースはほとんどありませんでした(紙は保管よりも安上がりでした)。ですから、これはこのトピックを初めて知っている人にとっては十分なものだと思います。ただし、最後の質問はここで聞いてもいいでしょう。残念ながら、@ Kusalanandaと私自身にはそれに対する答えがありません。
フィリポス

1
Comeon @Philippos。あなたは本当に彼がマンページを改善しなかったとKusalanandaに言っていますか?:-)
Zsolt Szilagy

1
@allo xargs時々便利ですが、findそれなしでコマンドに複数のパス引数を渡すことができます。-exec command... {} +(withの+代わりに\;command...適合する限り多くのパスを渡します(各OSには、コマンドラインの長さに制限があります)。のようxargsに、-で+終わる形式find-execアクションcommand...も、パスが多すぎて制限内に収まらないというまれなケースで複数回実行されます。
エリアケイガン

2
@ZsoltSzilagy私はそれを言ったことも意味もしなかった。彼はあなたをとてもよく食べさせてくれました、あなたは自分で食べるのに十分な年齢だと思います。(-;
フィリポス

回答:


90

この答えは次の部分にあります。

  • の基本的な使い方 -exec
  • -execと組み合わせて使用するsh -c
  • を使用して -exec ... {} +
  • を使用して -execdir

の基本的な使い方 -exec

-execオプションは、その引数と実行それとして、オプションの引数を指定して、外部ユーティリティを取ります。

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

コマンドは、それがどこで終了するかを知る;ためにfor で終了する必要がありますfind(後でさらにオプションがある場合があるため)。を;シェルから保護するには、\;またはとして引用する必要があります';'。そうしないと、シェルはそれをfindコマンドの最後として認識します。

例(\最初の2行の最後は、行の継続のためだけです):

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

これにより、現在のディレクトリ以下の-type fパターン*.txtに名前が一致するすべての通常ファイル()が検索されます。次に、hello使用されているファイルのいずれかで文字列が発生するかどうかをテストしますgrep -q(出力は生成されず、終了ステータスのみが生成されます)。文字列を含むファイルについてcatは、ファイルの内容を端末に出力するために実行されます。

また、それぞれがで-exec見つけたパス名の「テスト」のfindよう-type-name機能します。コマンドがゼロの終了ステータス(「成功」を示す)を返す場合、コマンドの次の部分findが考慮されます。それ以外の場合、findコマンドは次のパス名で続行されます。これは、上記の例で文字列を含むファイルを検索するために使用されますが、hello他のすべてのファイルを無視します。

上記の例は、次の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つの引数が渡されます。

  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" {} ';'

内部スクリプト内で、$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" {} ';'

この最後のバリエーションでは、変数fromto子シェルの変数は、外部スクリプトの同じ名前の変数とは異なることに注意してください。

上記から、任意の複雑なスクリプトを呼び出す正しい方法である-execfind。の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可能な限り数回実行されます。この最後の例ではmv、coreutilsのGNUが必要です(-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はベース名の先頭に./findそれはしません)。

例:

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

これにより、見つかった各ファイルが、ファイルが見つかった場所と同じディレクトリ内の*.txt既存のdone-textsサブディレクトリに移動します。また、ファイルにサフィックス.doneを追加することにより、ファイルの名前が変更されます。

これは、ファイルの新しい名前を作成するため-execに、見つかったファイルのベース名を取得する必要があるため、少しややこしいでしょう{}。ディレクトリを適切{}に見つけるには、ディレクトリ名fromも必要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 {} +

7
-execプログラムと引数を取り、実行します。一部のシェルコマンドはプログラムと引数のみで構成されていますが、多くは構成されていません。シェルコマンドには、リダイレクトとパイピングを含めることができます。-execできません(ただし、全体findをリダイレクトできます)。シェルコマンドでは; && ifetc を使用できます。-execできませんが、-a -oできます。シェルコマンドは、エイリアスまたはシェル関数、または組み込みコマンドです。-execできません。シェルコマンドは変数を展開できます。-execできません(ただし、find缶を実行する外側のシェル)。シェルコマンドは$(command)毎回異なる方法で置き換えることができます。-execできません。...
dave_thompson_085

...シェルコマンドはグロブできますが、-execできません- findほとんどのグロブと同じ方法でファイルを反復処理できますが、それはめったに望まれません。
-dave_thompson_085

@ dave_thompson_085もちろん、シェルコマンドをすることができsh、それ自体、capablyこれらすべての物事の完全である
Tavianバーンズ

2
ここでシェルコマンドは間違っていると言いfind -exec cmd arg \;、シェルコマンドラインを解釈するためにシェルを起動せず、execlp("cmd", "arg")直接実行しますexeclp("sh", "-c", "cmd arg")(シェルは組み込みされていないexeclp("cmd", "arg")場合と同等のことを行いcmdます)。
ステファンシャゼラス

2
あなたはすべてのことを明確にできたfind引数の後に-exec、最大;または+の各インスタンスで、その引数と一緒に実行するコマンド作る{}引数を(と、現在のファイルに置き換え;)、および{}前の最後の引数として+ファイルのリストに置き換え個別の引数として(この{} +場合)。IOW -execいくつかの引数を取り、;or で終了し{} +ます。
ステファンシャゼラス
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.