検索したファイルをまとめてコマンド実行したい

findコマンドで検索したファイルに対して、rmchmodなどのコマンドを実行したい場合がある。findコマンドの-execオプションに{} +を指定すると、検索結果のファイルをまとめて1回のコマンド実行にまとめられる。

-exec {} ; はファイル1つにつき1回コマンドを実行する

{}はマッチしたファイルパスに置き換わるプレースホルダー。末尾を\;にすると、マッチしたファイル1つにつき1回コマンドを実行する。

$ find . -name '*.log' -exec echo {} \;
./app3.log
./app1.log
./app2.log

3つの.logファイルに対して、echoコマンドが3回実行されている。

-exec {} + はファイルをまとめて1回コマンドを実行する

末尾を+にすると、マッチしたファイルをすべて1つのコマンドの引数にまとめて、コマンドを1回だけ実行する。

$ find . -name '*.log' -exec echo {} +
./app3.log ./app1.log ./app2.log

3つの.logファイルが1回のechoコマンドの引数として渡されている。wc -lと組み合わせると、複数ファイルの行数をまとめて集計できる。

$ find . -name '*.log' -exec wc -l {} +
 1 ./app3.log
 1 ./app1.log
 1 ./app2.log
 3 total

コマンドの実行回数を比較する

実際にコマンドが呼び出される回数を、呼び出しをログに記録するスクリプトで確認する。

$ cat counter.sh
#!/bin/bash
echo "called with: $@" >> call.log
$ chmod +x counter.sh

$ find . -name '*.log' -exec ./counter.sh {} \;
$ cat call.log
called with: ./app3.log
called with: ./app1.log
called with: ./app2.log

$ rm call.log
$ find . -name '*.log' -exec ./counter.sh {} +
$ cat call.log
called with: ./app3.log ./app1.log ./app2.log

\;ではcounter.shが3回呼び出されているのに対し、+では1回にまとめて呼び出されている。対象ファイルが大量にある場合、+を使うとコマンドの起動回数を減らせるため、処理が速くなる。

{} はコマンドの末尾に1つだけ書く

+を使う場合、{}はコマンドの末尾に1つだけ書く必要がある。{}の後に別の引数を続けるとエラーになる。

$ find . -name '*.log' -exec echo {} extra +
find: missing argument to `-exec'

{}より前に固定の引数を置くことは可能。

$ find . -name '*.log' -exec echo before {} +
before ./app1.log ./app2.log

ファイル名にスペースを含む場合も安全に扱える

-exec {} +はファイル名をコマンドの引数として直接渡すため、ファイル名にスペースが含まれていても安全に扱える。

$ touch 'my file.log' 'another file.log'

$ find . -name '*.log' -exec ls -l {} +
-rw-r--r-- 1 root root 0 Jul  5 13:36 ./another file.log
-rw-r--r-- 1 root root 0 Jul  5 13:36 ./my file.log

findの結果をパイプでxargsにそのまま渡す方法では、スペースを含むファイル名が分割されて壊れる場合がある。

$ find . -name '*.log' | xargs ls -l
ls: cannot access './my': No such file or directory
ls: cannot access 'file.log': No such file or directory
ls: cannot access './another': No such file or directory
ls: cannot access 'file.log': No such file or directory

用途

-exec {} +は以下のような場面で役立つ。

  • 検索した大量のファイルに対して、コマンドの起動回数を抑えて処理を高速化したい場合
  • 複数ファイルの行数集計など、複数ファイルを1つのコマンドにまとめて渡したい場合
  • ファイル名にスペースなど特殊な文字が含まれていても、安全にコマンドへ渡したい場合

更新日時を基準にファイルを検索したい場合は【find -mtime】指定日数より古い(新しい)ファイルを検索する も参照。

ファイルサイズを基準に検索したい場合は【find -size】指定サイズ以上(以下)のファイルを検索する も参照。