きゃまなかのブログ

新卒6年目の WEB エンジニアです。 Ruby on Rails の TIPS を中心にブログ書いてます。 去年まで運用・保守のエンジニアだったので、サーバサイドの記事もたまに書きます。 よろしくお願いします。

【Linux】xargs コマンドの使い方がよく分からない

概要

結構複雑な処理をサーバーサイドでやりたいなと思って検索すると xargs コマンドを利用するケースが多々見受けられます。

ただし、使う機会は多くないので、検索して見つけたコマンドをコピペして使っていたり、xargs コマンドが何者なのかよく理解していませんでした。

折角の機会なので xargs コマンドの使い方をまとめました。

xargs とは

前のコマンド (command1) の実行結果を標準入力 (stdin) から受け取って、次のコマンドの引き数に渡す (stdout) 仲介役をしてくれるコマンドです。

f:id:kyamanak83:20180129224458p:plain

参考:

www.atmarkit.co.jp

よく見る利用法

よく使われるのは、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 コマンドを使った方が、複数のファイルを一度に削除してくれるので高速です。

参考:

qiita.com

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 の値を見て良い感じに処理を分割して実行してくれるので文字数の制限に引っかからないらしいです。

参考:

qiita.com

ドライランオプション

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/ 以下にコピーする例となります。

便利なワンライナー

個人的に良く使うワンライナーなので紹介します。

Ruby on Rails の様なフレームワークを好んで使う人には使い道があると思います。

/var/rails_app/ 配下で hogehoge という文字列を全て fugafuga に置換してくれます。

$ grep -rl 'hogehoge' /var/rails_app/ | xargs perl -i -pe "s/hogehoge/fugafuga/g" 

例えば、1 つのファイルで共通メソッドを管理していたとします。

※ Ruby on Rails の場合は app/controllers/application.rb を想像して下さい。

もし、共通メソッドの名前を変更した場合、そのメソッドを呼び出している箇所を全て探して修正する必要があります。

何処で呼び出しているか分からない場合、まずは grep コマンドで検索しますよね?

その後、検索でヒットしたファイルをいちいち vim とかで開いて修正するのは面倒臭いです。

この一連の作業を一発でやってくれるのが、このワンライナーです。

最後に

今回は削除(rm コマンド)をベースに話しましたが、他のコマンドの引数に渡すことも良くやると思います。

xargs コマンドを使えるようになって、最近はログ調査で、その便利さをしみじみ感じています。

サーバーサイドのエンジニアは作業効率の幅が広がるので、良く分からないと思っている人も使い始めてみることをオススメします。