find -exec と find | xargs との違い

findにおける-execとxargsの違いの解説ページアイキャッチ

このページでは、find コマンド実行時によく利用される -exec と | xargs との違いについて解説していきます。

-exec に関してはコマンドの終端文字が \; の場合と + とで動作が異なりますので、この違いについても解説していきます。

つまり、このページでは下記の3つの違いについて解説していきます。

  • -exec コマンド {} \;
  • -exec コマンド {} +
  • | xargs コマンド

これらを利用することで、find で検索された全ファイルに対して -exec| xargs の後ろに指定したコマンドを実行することができるようになります。

例えば、下記の3つは全て、find で検索された拡張子 .txt のファイルに rm を実行するコマンドとなります。

-exec {}\;の使用例
% find . -name "*.txt" -exec rm {} \;
-exec {}+の使用例
% find . -name "*.txt" -exec rm {} +
xargsの使用例
% find . -name "*.txt" | xargs rm

特別なケースを除いて、最終的な結果としては3つとも同じになります(特別なケースについては後述で解説します)。

ただし、それでも上記の3つではコマンド実行時の動作が異なります。

どのように動作が異なるのか?この点について解説していきます。

実例で違いを確認

最初に find コマンドと下記の3つを併用した際の動作の違いを実例を示しながら確認していきたいと思います。

  • -exec コマンド {} \;
  • -exec コマンド {} +
  • | xargs コマンド

今回は、下記の find コマンドを実行した結果、

% find . -name "*.txt"

下記の4つのファイルが検索結果として見つけられるようなファイル構成においての、各コマンドの動作の違いについて確認していきたいと思います。

  • ./test1.txt
  • ./test2.txt
  • ./test3.txt
  • ./te st4.txt

つまり、カレントディレクトリに上記の4つのファイルが存在するというわけですね!

また、4つ目のファイルパスの途中にスペースがありますが、これはわざとです。

では、このようなファイル構成において下記の3つを利用し、find で見つけたファイル全てに対して rm コマンドを実行する場合の動作について確認していきましょう。

  • -exec コマンド {} \;
  • -exec コマンド {} +
  • | xargs コマンド

-exec コマンド {} \; の使用例

まずは -exec コマンド {} \; を利用して下記のコマンドを実行した時の動作について考えてみましょう。

-exec \;
% find . -name "*.txt" -exec rm {} \;

この場合、実際には rm コマンドは下記のように find コマンドで見つけたファイルの数の分だけ実行されることになります。

% rm "./test1.txt"
% rm "./test2.txt"
% rm "./test3.txt" 
% rm "./te st4.txt"

スポンサーリンク

-exec コマンド {} + の使用例

それに対し、-exec コマンド {} + を利用して下記のコマンドを実行した場合、

-exec {}+
% find . -name "*.txt" -exec rm {} +

イメージとしては下記のように rm が実行されることになります。つまり、find コマンドで見つけたファイルのパスを並べて引数として指定して rm コマンドが1度だけ実行されることになります。

% rm "./test1.txt" "./test2.txt" "./test3.txt" "./te st4.txt"

| xargs の使用例

さらに、| xargs を利用して下記のコマンドを実行した場合、

xargsの使用例
% find . -name "*.txt" | xargs rm

下記のように rm が実行されることになります。つまり、find コマンドで見つけたファイルを並べて引数として指定して rm コマンドが1度だけ実行されることになります。

% rm ./test1.txt ./test2.txt ./test3.txt ./te st4.txt

ただし、xargs の場合、./te st4.txt の空白を区切りに別々の引数として rm が実行されることになります。

つまり、上記のコマンドでは ./test4.txt に対して rm コマンドが実行されることになります(実際にはそのようなファイルは存在しないのでエラーが発生する)。

簡単な例でしたが、この例は3つの動作の違いを端的に示す良い例だと思います。続いては、下記の3つについて個別に説明していきたいと思います。

  • | xargs コマンド
  • -exec コマンド {} \;
  • -exec コマンド {} +

-exec コマンド {} \;

実例で違いを確認 で紹介した例からも分かるように、-exec コマンド {} \; を利用した場合、find コマンドで見つけられた1つのファイルのパスに対して -exec の後ろに指定したコマンドが1回実行されることになります。

execコマンド{};でのコマンドの実行のされ方の例

つまり、find コマンドで見つけられたファイルの数の分だけ -exec の後ろ側のコマンドが実行されることになります。そしてこの時、find コマンドで見つけられたファイルのパスが、その後ろ側のコマンドの引数に指定されることになります。

スポンサーリンク

スポンサーリンク

速度は遅い

find コマンドで見つけたファイルの数だけ -exec の後ろに指定したコマンドが繰り返し実行されることになるため、-exec コマンド {} \; は遅いです。

コマンドを実行する際には、まずプログラムを起動するという処理が行われることになります。そして、このプログラムの起動には多少ではあるものの時間がかかります。

find コマンドでファイルが見つけられる度にプログラムの起動が行われる -exec コマンド {} \; は、特に find コマンドで見つけられるファイルの数が多い場合は処理時間が長くなってしまいます。

例えば下記のコマンドで 2000 個のファイルに対して rm が実行されるとした時(つまり find コマンドで 2000 個のファイルが見つけられる)、私の環境では下記の実行にかかる時間は約 6.8 秒でした。

% find . -name "*.txt" -exec rm {} \;

後述で説明するように、-exec コマンド {} + の場合は実行にかかる時間は 0.3 秒です。

この結果からも、-exec コマンド {} \; の遅さを理解していただけるのではないかと思います。

引数の個数が固定になる

ここまでの説明を聞いて、「-exec コマンド {} \; は遅いから使わない方が良い」と思われるかもしれませんが、全く使い道がないというわけではないです。

-exec コマンド {} \; の良いところは、-exec の後ろ側のコマンドに対して指定される「引数の個数が固定になる」という点にあると思います。-exec コマンド {} \; では、-exec の後ろ側のコマンドに対して指定されるファイルパスは毎回1つになります。

例えば rm コマンドは引数の個数が最大値を超えない限り、受け取れる引数の個数は可変となります(0 だとダメだけど)。なので、指定する引数の個数は固定である必要はありません。Linux や Mac 等に用意されている多くのコマンドでは、受け取れる引数の個数が可変となっています。

ですが、-exec の後ろ側に指定して実行できるのは Linux や Mac 等に用意されているコマンドだけではありません。例えば「自身で作成したプログラム」を -exec に指定し、find で見つけたファイルのパスを引数に指定してそのプログラムを実行させるようなことも可能です。

例えば下記を実行すれば、find で見つけたファイルのパスを引数に指定してカレントディレクトリの main.exe が実行されることになります。

% find . -name "*.txt" -exec ./main.exe {} \;

こういった、自身で作成したプログラムや友達・チームのメンバー等が作成したプログラムに関しては、引数の個数を可変ではなく固定であることを前提に作成されていることもあります。

例えば引数が特定の個数でなければプログラムを終了することも多いです。

こういった引数の個数が可変ではなく「固定である」ことを前提に作成されているプログラムを -exec に指定したい場合でも、-exec コマンド {} \; を用いれば確実に実行させることができます。

-exec コマンド {} +

-exec コマンド {} \; ではファイル1つ1つに対して毎回 -exec の後ろ側のコマンドが実行されるのに対し、-exec コマンド {} + を利用した場合は find コマンドで見つけたファイルを一度にまとめて -exec の後ろ側のコマンドの引数に指定して実行されることになります。

「execコマンド{}+」でのコマンドの実行のされ方の例

ただし、コマンドに指定可能な引数の個数には上限があります。

find コマンドで見つけたファイルが多すぎて引数に指定可能な個数の上限を超えるような場合は、エラーにならないように複数回に分けて -exec の後ろ側のコマンドが実行されることになります。

引数に指定可能な個数の上限を超えない場合は、-exec の後ろ側のコマンドの実行は一回のみとなります。

スポンサーリンク

スポンサーリンク

速度は遅い

いずれにせよ、-exec の後ろ側のコマンドが実行される回数は -exec コマンド {} \; を利用した時よりも少なくなり、その分 -exec コマンド {} + の方が処理が高速になります。

例えば下記のコマンドで 2000 個のファイルに対して rm が実行されるとした時、私の環境では下記を実行するのにかかる時間は約 0.3 秒でした。

% find . -name "*.txt" -exec rm {} +

-exec コマンド {} \; でも説明したように、-exec コマンド {} \; の場合は実行にかかる時間は 6.8 秒でしたので、exec コマンド {} + の方が断然高速ですね!

特に find で見つけられるファイル数が多い場合は -exec コマンド {} \; よりも exec コマンド {} + を積極的に利用する方が良いです。

引数の個数は find の結果によって異なる

また、これも -exec コマンド {} \; と裏返しの話になりますが、-exec コマンド {} + の場合は -exec の後ろ側のコマンドに指定される引数の個数は find で見つけられるファイルの数によって変わります。

そのため、「引数の個数が固定である」ことを前提として作られたコマンドやプログラムを -exec に指定すると意図通りに動作しない可能性が高いので注意してください。

ただ、Linux や Mac にあらかじめ用意されているコマンドに関しては、ほとんどのものは引数の個数が可変となるように作られていると思います。なので、そこまで気にするほどのデメリットにはならないのではないかと思います。

ですが、引数の指定の仕方が -exec コマンド {} \;-exec コマンド {} + とで異なることはしっかり覚えておきましょう!

これを覚えておけば、-exec コマンド {} + を利用して -exec の後ろ側に指定したコマンドやプログラムが意図通りに動作しないような場合でも(例えば1つのファイルにしか処理が行われないような場合でも)、解決方法をすぐに見出せるようになると思います(exec コマンド {} \; を利用して引数の個数を固定にすれば解決できるかもしれないですよね!)。 

| xargs コマンド

最後に | xargs コマンド について解説していきます。

スポンサーリンク

-exec コマンド {} + と似ているが空白文字等の扱いに注意 

| xargs コマンド はコマンドの実行の仕方や引数の指定の仕方は -exec コマンド {} + と似ています。

| xargs コマンド を利用した場合、-exec コマンド {} + 同様に find コマンドで見つけたファイルをまとめて一度に | xargs の後ろ側のコマンドの引数に指定して実行されることになります。

なので、メリットデメリットも -exec コマンド {} + と同様であると言えます。

ただ、似ているものの違いはあります。

それは、| xargs では空白文字(スペースや改行など)を引数の区切りとして使用する点になります。

このため、find コマンドで見つけたファイルのパスに空白文字が含まれる場合、空白文字を区切りとして異なる引数として | xargs の後ろ側のコマンドに指定されることになります。

「|xargs コマンド」でのコマンドの実行のされ方の例

それに対し、-exec コマンド {} + の場合、必ず find コマンドで見つけたファイルのパスがそのまま1つの引数として指定されます。なので、パスに空白文字等の区切り文字が含まれていたとしても、1つのパスとして -exec  の後ろ側のコマンドに引数として指定されることになります。

ただ、| xargs でもオプションを利用することで、-exec コマンド {} + 同様にパスの中に空白文字等がある場合も1つの引数として扱うことが可能になります。

具体的には、find コマンドに -print0 オプションを、さらに xargs コマンドに -0 オプションを指定することで、パスの中に区切り文字等があった場合も1つの引数として扱うことが可能になります。

% find . -name "**.txt" -print0 | xargs -0 コマンド

xargs-0 オプションを指定することで、xargs では入力された文字列をヌル文字(下の図における \0)という特殊な文字を区切りに1つの引数として扱うようになります。

xargsに-0オプションを付与する効果を説明する図

さらに、find-print0 オプションを指定することで、find は見つけたファイルの各パスを改行ではなくヌル文字区切りで出力するようになります。

findに-print0オプションを付与する効果を説明する図

これにより、パスの中に空白文字が存在した場合でも、xargs ではその空白文字を区切り文字ではなく単なる文字として扱い、ヌル文字が現れるまでを1つの引数として扱うようになります。

このため、find が見つけたファイルのパスをそのまま xargs の後ろ側のコマンドに引数として指定することができるようになります。

xargs は汎用性が高い

ここまで -exec| xargs との比較を行なってきましたが、実はこれらは全く種類の異なるものです。

-execfind のオプションの1つです。

そのため -exec は他のコマンドと併用できない場合も多く、find 以外ではほとんど使い道のないものと言えると思います。

それに対して xargs は、find 同様にコマンドの1つとなります。

xargs をパイプ | と併用することで | xargs の前側のコマンドの出力結果を | xargs の後ろ側のコマンドの引数に変換し、複数のコマンドを連携して動作させることができます。

この | xargs に関しては find 以外のコマンドとも併用可能であり、使い所は -exec よりも多いと思います。そして、複数のコマンドを連携して動作させるために非常に重要なコマンドですので、| xargs に関しては必ず覚えておいた方が良いと思います。

| xargs やパイプ | については下記ページで解説していますので、詳しく知りたい方はぜひ下記ページを読んでみてください。

パイプの解説ページアイキャッチパイプ | について解説( | と | xargs の違いも理解できる!)

-exec {} \;-exec {} +| xargs の違い

最後に -exec {} \;-exec {} +| xargs の3つの違いについてまとめておきます。

スポンサーリンク

コマンドの実行の仕方

find で見つけたファイルに対し、-exec {} \;-exec {} +| xargs それぞれのコマンドの実行の仕方は下記のようになります。

  • -exec {} \;:1つのファイルに対して1回実行
  • -exec {} +:複数のファイルに対して1回実行
  • | xargs:複数のファイルに対して1回実行

後者の2つに関しては、引数の個数が上限以下となるようにコマンドの実行回数が自動的に変化します(引数の個数が上限を越えない場合は全てのファイルに対して1回のみ実行される)。

引数の指定の仕方

find で見つけたファイルに対し、-exec {} \;-exec {} +| xargs それぞれのコマンドへの引数の指定の仕方は下記のようになります。

  • -exec {} \;:1つの引数のみを指定
  • -exec {} +:複数のファイルをまとめて指定
  • | xargs:複数のファイルをまとめて指定

後者の2つに関しては、引数の個数が上限以下となるように自動的に引数の個数が設定されます(引数の個数が上限を越えない場合は全てのファイルに対して1回のみ実行される)。

| xargs の場合、特定のオプションを利用しない場合は空白文字で引数が区切られて指定されることになるので注意してください。

特徴

-exec {} \;-exec {} +| xargs それぞれの特徴もまとめておきます。

  • -exec {} \;:遅い・引数の個数が固定
  • -exec {} +:速い・引数の個数が変動
  • | xargs:速い・引数の個数が変動・空白文字の扱いに注意・他のコマンドにも併用可能

スポンサーリンク

まとめ

このページでは、find とよく併用される下記の3つについて解説しました!

  • -exec コマンド {} \;
  • -exec コマンド {} +
  • | xargs コマンド

これらの3つでは -exec| xargs に指定したコマンドの実行の仕方やコマンドへの引数の渡し方が異なります。

コマンドの実行の仕方やコマンドへの引数の渡し方が異なるために実行速度が異なりますし、特定のケースでは -exec| xargs に指定したコマンドが意図した通りに動作しないようなこともあります。

find と併用するのであれば、とりあえず一番無難なのは -exec コマンド {} + だと思います。

ただし、-exec に指定するコマンド(やプログラム)が「引数の個数が固定である」ことを前提としているものである場合、-exec コマンド {} + ではうまく動作させられない可能性があるので注意してください。

-exec に指定するコマンド(やプログラム)に指定する引数の個数を固定としたい場合は -exec コマンド {} ; を利用するのが良いと思います。

一番大事なのは、上記の3つにおける「コマンドの実行の仕方」や「コマンドへの引数の渡し方」の違いをしっかり理解しておくことだと思います。

この違いを理解しておけば、例えば下記のような問題や要望が発生した際に、自然と解決策も思いつくようになると思います。

  • コマンドの実行に時間がかかりすぎる
  • パスにスペースが含まれていてうまく動作しない
  • find 以外でも、コマンドの実行結果を別のコマンドの引数に指定したい

なので、是非これらの3つの違いについてしっかり理解しておき、問題等が発生した際に解決できるようにしておきましょう!

同じカテゴリのページ一覧を表示

コメントを残す

メールアドレスが公開されることはありません。