概要
結構複雑な処理をサーバーサイドでやりたいと思って検索すると、xargs コマンド使ったワンライナーを良く見かけます。
ただし、xargs コマンドが何者なのか理解することなく、今まで検索したワンライナーをコピペして使ってきました。
折角なので xargs コマンドの使い方をまとめました。
xargs とは
前のコマンド (command1) の実行結果を標準入力 (stdin) から受け取って、次のコマンドの引き数に渡す (stdout) 仲介役をしてくれるコマンドです。
参考:
よく見る利用法
よく使われるのは、find コマンドと組み合わせて、名前検索でヒットしたファイルを標準入力から受け取って、削除する方法かな思います。
$ find . -name "*.log" | xargs rm -fv
'./access.log' を削除しました
'./error.log' を削除しました
自分はこのワンライナーで初めて xargs コマンドに触れました。
そして、これなら rm *.log だけで良くない?xargs コマンドを使うメリットってどこにあるの?って混乱したのを覚えています。
確かに、このワンライナーと実行結果を見ると rm コマンド単体で問題ありません。
しかし、find コマンドは名前検索以外にも様々な条件でファイルを検索することが出来るので、xargs コマンドと組み合わせると作業効率の幅が広がります。
- タイムスタンプで検索できる
- パーミッションで検索できる
- ファイルサイズで検索できる(空ファイルも検索できる)
- 検索する階層を指定できる
ネットで検索した xargs コマンドのサンプルには、このようにメリットが感じられないサンプルも多々あります。
それを見て、こうやって使うのか!って鵜呑みにしたことが当時間違えでした。
高速に並列処理できる
find コマンドには exec オプションと言うものがあり、検索でヒットしたファイルを、exec オプション以下のコマンドに渡して実行することができます。
$ find . -name "*.log" -exec rm -fv {} \;
'./access.log' を削除しました
'./error.log' を削除しました
一見、xargs コマンドと同じですが、内部的な処理が違います。
exec オプションを使った場合
$ rm -fv access.log
$ rm -fv error.log
rm コマンドを 2 回叩いたのと同様です
xargs コマンドを使った場合
$ rm -fv access.log error.log
rm コマンドの引数に 2 つのファイルを指定したのと同様です。
この 2 つのコマンドは大量のファイル数を削除する場合に明確な差が出ます。
xargs コマンドを使った方が、複数のファイルを一度に削除してくれるので高速です。
参考:
Argument list too long の制限を受けない
大量のファイルを rm *.log で削除しようとすると、Argument list too long と怒られることがあります。
これはコマンドの引数の文字列が長すぎるせいです。
rm *.log を実行した場合、内部的には rm aaa.log bbb.log ccc.log ddd.log .... を実行しています。ファイル数が多い場合、この文字列はどうしても長くなります。
OS 毎に引数に指定できる文字数は決まっており、その文字数を超えるとエラーとなります。
以下のコマンドでその文字数を確認できます。
$ getconf ARG_MAX
xargs コマンドは、この ARG_MAX の値を見て良い感じに処理を分割して実行してくれるので、文字数の制限に引っかからないらしいです。
参考:
ドライランオプション
xargs コマンドには ドライランオプションがあります。
前のコマンドの出力結果が不確かな場合や、自分が意図した処理を実行してくれるが心配な場合、p オプションを付けることで生成されるコマンドを確認することができます。
$ find . -name "*.log" | xargs -p rm -fv
rm -fv ./error.log ./access.log?... # 上のコマンドはこのコマンドを叩くのと同様
p オプションは生成されるコマンドを確認するだけで、実際には実行されません。
確認して問題なければ p オプションを外して実行してください。
引数の場所を指定する
rm コマンドを実行する場合、引数が 1 つなので問題ありませんが、cp コマンドや mv コマンドを実行する場合は引数が 2 つ必要になります。
そのような場合、i オプションを使って、xargs コマンドで受け取った値を引数に指定する位置を決める必要があります。{} に受け取った値が入ります。
$ find . -name "*.log" | xargs -i cp {} /tmp/.
これは find コマンドで受け取ったファイルを /tmp/ 以下にコピーする例となります。
便利なワンライナー
個人的に一番良く使うワンライナーです!
カレントディレクトリ以下のファイルから "hogehoge" という文字列を含むファイルを探し出して、そのファイルの "hogehoge" を全て "fugafuga" に置換してくれます。
$ grep -rl 'hogehoge' ./* | xargs perl -i -pe "s/hogehoge/fugafuga/g"
自分は Ruby on Rails を好んで開発していますが、アプリケーション内で何度も使うメソッドは app/controllers/application.rb で管理しています。
もし、app/controllers/application.rb 内に書かれている共通メソッドの名前を変更した場合、そのメソッドを呼び出している箇所を全て探し出して vim とかで修正するのは面倒です。
そう言った時にこのワンライナーを使って一発で置換します。
最後に
今回は rm コマンドをベースに話しましたが、他のコマンドの引数に渡すことも良くやると思います。
xargs コマンドを使えるようになって、運用面でかなり幅が広がりました。
特にログ調査とかで 1 週間以内のアクセスログから、この URL にリクエストしたユーザ数を抽出するとか、そう言った時に凄く便利です。
最近だと Splunk とかログ解析系の PF が沢山あるので、サーバにログインしてコマンドで解析したりすることは減ってきているかもしれませんが。
サーバーサイドのエンジニアは作業効率化に繋がりますので、使いこなせるように理解して下さい。