このページでは、find
コマンド実行時によく利用される -exec
と | xargs
との違いについて解説していきます。
-exec
に関してはコマンドの終端文字が \;
の場合と +
とで動作が異なりますので、この違いについても解説していきます。
つまり、このページでは下記の3つの違いについて解説していきます。
-exec コマンド {} \;
-exec コマンド {} +
| xargs コマンド
これらを利用することで、find
で検索された全ファイルに対して -exec
や | xargs
の後ろに指定したコマンドを実行することができるようになります。
例えば、下記の3つは全て、find
で検索された拡張子 .txt
のファイルに rm
を実行するコマンドとなります。
% find . -name "*.txt" -exec rm {} \;
% find . -name "*.txt" -exec rm {} +
% find . -name "*.txt" | xargs rm
特別なケースを除いて、最終的な結果としては3つとも同じになります(特別なケースについては後述で解説します)。
ただし、それでも上記の3つではコマンド実行時の動作が異なります。
どのように動作が異なるのか?この点について解説していきます。
Contents
実例で違いを確認
最初に 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 コマンド {} \;
を利用して下記のコマンドを実行した時の動作について考えてみましょう。
% find . -name "*.txt" -exec rm {} \;
この場合、実際には rm
コマンドは下記のように find
コマンドで見つけたファイルの数の分だけ実行されることになります。
% rm "./test1.txt" % rm "./test2.txt" % rm "./test3.txt" % rm "./te st4.txt"
スポンサーリンク
-exec コマンド {} +
の使用例
それに対し、-exec コマンド {} +
を利用して下記のコマンドを実行した場合、
% find . -name "*.txt" -exec rm {} +
イメージとしては下記のように rm
が実行されることになります。つまり、find
コマンドで見つけたファイルのパスを並べて引数として指定して rm
コマンドが1度だけ実行されることになります。
% rm "./test1.txt" "./test2.txt" "./test3.txt" "./te st4.txt"
| 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
が実行されることになります。
つまり、上記のコマンドでは ./te
と st4.txt
に対して rm
コマンドが実行されることになります(実際にはそのようなファイルは存在しないのでエラーが発生する)。
簡単な例でしたが、この例は3つの動作の違いを端的に示す良い例だと思います。続いては、下記の3つについて個別に説明していきたいと思います。
| xargs コマンド
-exec コマンド {} \;
-exec コマンド {} +
-exec コマンド {} \;
実例で違いを確認 で紹介した例からも分かるように、-exec コマンド {} \;
を利用した場合、find
コマンドで見つけられた1つのファイルのパスに対して -exec
の後ろに指定したコマンドが1回実行されることになります。
つまり、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
の後ろ側のコマンドの引数に指定して実行されることになります。
ただし、コマンドに指定可能な引数の個数には上限があります。
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
の後ろ側のコマンドに指定されることになります。
それに対し、-exec コマンド {} +
の場合、必ず find
コマンドで見つけたファイルのパスがそのまま1つの引数として指定されます。なので、パスに空白文字等の区切り文字が含まれていたとしても、1つのパスとして -exec
の後ろ側のコマンドに引数として指定されることになります。
ただ、| xargs
でもオプションを利用することで、-exec コマンド {} +
同様にパスの中に空白文字等がある場合も1つの引数として扱うことが可能になります。
具体的には、find
コマンドに -print0
オプションを、さらに xargs
コマンドに -0
オプションを指定することで、パスの中に区切り文字等があった場合も1つの引数として扱うことが可能になります。
% find . -name "**.txt" -print0 | xargs -0 コマンド
xargs
に -0
オプションを指定することで、xargs
では入力された文字列をヌル文字(下の図における \0
)という特殊な文字を区切りに1つの引数として扱うようになります。
さらに、find
に -print0
オプションを指定することで、find
は見つけたファイルの各パスを改行ではなくヌル文字区切りで出力するようになります。
これにより、パスの中に空白文字が存在した場合でも、xargs
ではその空白文字を区切り文字ではなく単なる文字として扱い、ヌル文字が現れるまでを1つの引数として扱うようになります。
このため、find
が見つけたファイルのパスをそのまま xargs
の後ろ側のコマンドに引数として指定することができるようになります。
xargs
は汎用性が高い
ここまで -exec
と | xargs
との比較を行なってきましたが、実はこれらは全く種類の異なるものです。
-exec
は find
のオプションの1つです。
そのため -exec
は他のコマンドと併用できない場合も多く、find
以外ではほとんど使い道のないものと言えると思います。
それに対して xargs
は、find
同様にコマンドの1つとなります。
xargs
をパイプ |
と併用することで | xargs
の前側のコマンドの出力結果を | xargs
の後ろ側のコマンドの引数に変換し、複数のコマンドを連携して動作させることができます。
この | xargs
に関しては find
以外のコマンドとも併用可能であり、使い所は -exec
よりも多いと思います。そして、複数のコマンドを連携して動作させるために非常に重要なコマンドですので、| 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つの違いについてしっかり理解しておき、問題等が発生した際に解決できるようにしておきましょう!