いまさらxargsの便利さを主張してみる

タイトルの通りですが、xargsコマンドの便利さを紹介する記事を書いてみました。xargsは私が大好きなコマンドの一つで、標準入力から渡されたファイル名などを引数とみなして、別のコマンドの引数として起動するというものです。

例えばfindで見つけたファイルを全部削除したい場合、xargsなしでも下記のように書くことができます。

$ rm `find . -name \*~`

しかし、これではヒットするファイル数が多すぎる場合に、下記のようなエラーを出して停止することがあります。

-bash: /bin/rm: Argument list too long

普段は意識しないと思いますけど、実はコマンド実行する際の引数の総バイト数には上限があります。上のエラーメッセージは、この限界を超えたという意味です。長時間findを実行した挙句に上のようなエラーが出て停止すると非常に切ない気持ちになりますね。

一方で、同じ目的であれば下記のようにしても達成できます。

$ find . -name \*~ -exec rm {} \;

ただ、これだと100000ファイルがヒットした場合にrmコマンドが100000回実行されてしまうため、全部実行するまでに案外時間がかかる、なんてことがあります。OSがプロセスを起動/終了するコストは比較的高いので、実際にファイルを消している時間は短いのにプロセスを100000回起動/終了するのに時間がかかる、ということが起こります。

一気に全部rmするにはファイル数が多すぎるけど、1つづつrmしていたら時間がかかる、どこのご家庭でも共通の悩みですよね。ところが奥さん、そんなときxargsがあれば大丈夫なんです!

$ find . -name \*~ | xargs rm

xargsを使った場合に上の2つの例とどう違うかというと、コマンド実行時の引数の制限を気にしつつ、rmにギリギリの個数のファイル名を渡して、必要最低限の回数だけ実行してくれます。100000ファイル一気にはrmできない場合でも、例えば25000ファイルづつに分けて4回rmしてくれます。安心かつ最速というわけで、xargsは便利ですよね!

以上を「レシピ」としてまとめると下記のようになります。

レシピ1:カレントディレクトリ以下にある、最後の1文字が「~」である全てのファイルを「ls -la」する。

$ find . -name \*~ | xargs ls -la
-rw-rw-r--  1 hanawa hanawa 29 Feb 19 18:23 ./c/test.c~
-rw-rw-r--  1 hanawa hanawa  9 Feb 19 18:21 ./hogehoge.txt~

ちなみに、このレシピは十分実用的な例です。xargsを利用してrmのような取り返しのつかないコマンドをいきなり実行するのは怖いので、「xargs rm」する直前に「xargs ls -la」などで対象ファイルを確認しておけばミスがありません。期待通りのファイルが「ls -la」されているのを確認した後で、シェルのヒストリを編集して「ls -la」を「rm」に書き換えて実行するわけです。

空白を含んだファイル名を扱いたいとき

このようにxargsは便利なのですが、空白を含む文字列を扱おうとすると困ることがあります。

$ find . -name \*~ | xargs rm

例えば、「Hello World.php~」というファイルがある場合に上記のコマンドを実行すると下記のようなエラーが出てしまいます。

rm: cannot remove `./Hello‘: No such file or directory
rm: cannot remove `World.php~’: No such file or directory

xargsコマンドは標準で空白文字(改行、スペース、タブなど)を区切り文字と見なします。このように空白文字を含むファイルがあると「./Hello」と「World.php~」のようにファイル名を2引数に分割してrmコマンドに渡してしまうため、期待通りに処理されません。これを解決する方法があります。

$ find . -name \*~ -print0 | xargs -0 rm

こうすると、findの出力がヌル文字(0×00、別の表現をすると”\0″)で区切られて出力されます。また、xargsも引数がヌル文字で区切られているものとして処理を行います。その結果、空白文字を含んだファイル名も正常に処理できます。

やっぱりxargsって便利ですね!

レシピ2:カレントディレクトリ以下にある、最後の1文字が「~」である全てのファイルを「ls -la」する。ただし、「Hello World.php~」のような、空白文字を含んだファイルが存在する。

$ find . -name \*~ -print0 | xargs -0 ls -la
-rw-rw-r--  1 hanawa hanawa 29 Feb 19 18:23 ./c/test.c~
-rw-rw-r--  1 hanawa hanawa  9 Feb 19 18:21 ./hogehoge.txt~
-rw-rw-r--  1 hanawa hanawa 20 Feb 19 18:24 ./php/Hello World.php~

xargsでmvを実行したいとき

さて、findで見つけたファイル全部をmvで/var/tmpに移動させたい、なんて場合も考えられます。実行したいコマンドがrmだったらxargsで問題ないんですが、mvの場合は最後の引数に移動先のディレクトリ名を書く必要があり、xargsでは使えないと思いがちですよね。少なくとも私はそう思っていました。

実は、そんなときのためにfindの-execオプションのような記法がxargsには用意されています。

$ find . -name \*~ -print0 | xargs -0 -I{} mv {} /var/tmp

しかし、これで出来ることは出来たわけですが、あまり嬉しくありません。というのも、これだと1ファイルづつ実行してしまうので、xargsを使う甲斐がありません。この例であればfindの-execで指定した方が見やすくて良いと思います。

じゃあどうすればいいんでしょうか。実は、GNU coreutilsのcpやmvにはxargsと組み合わせて使うためのオプションが用意されています。coreutilsのinfoにも書いてありますが、「-t」または「–target-directory」オプションでdestinationのディレクトリを指定できます。

$ find . -name \*~ -print0 | xargs -0 mv -t /var/tmp

僕はMac使いですけど、MacPortsでGNU coreutilsをインストールしています。こんなメリットがあるんだから標準のmvを捨ててGNUに浮気するのも当然ですよね?

言うまでもありませんが、この例ではディレクトリ構造を維持せず、ファイルのみ移動します。同名のファイルがあると期待通りには動きませんので注意が必要です。状況によりますが、一回tarで対象ファイルをアーカイブしてから目的のディレクトリで展開する方が希望に近いかもしれません(その場合も当然xargsを使うわけですが)。

いまさら感が漂う記事でしたが、いかがだったでしょうか。xargsに関して簡単な紹介と、使っているうちに出てくるであろう2大疑問を解決したつもりです(他に何か使っていて疑問点ってありますかね?)。xargsコマンドは敷居が高いのか、意外と使っている人が少ない印象があるんですよ。僕の感覚ではUNIXらしいコマンドの代表格だと思うんですけどね。もっとxargsを!

追記1: ブックマークコメントで、findの-print0とxargsの-0は伝統的UNIXの各コマンドには無いんじゃないか、というようなコメントを頂きました(現時点ではコメント削除されているかもしれませんが)。FreeBSDやMacOSX標準のfind/xargsには存在するみたいですけど、Solarisの各コマンドには無いみたいですね(今手元にSolarisマシンが無いので実際に確認はできていませんけど)。というわけで「Linux」タグを足しておきました。

コメント / トラックバック 8 件

#1 takano32 2008/02/21 05:37

-execオプションのとこともたまには思い出してあげてください・・・

#2 takano32 2008/02/21 05:38

って、斜め読みしすぎました・・・orz

#3 hujikojp 2008/02/21 07:48

hnwさん、ちと反応して重箱の隅をつついてしまいました:
http://d.hatena.ne.jp/hujikojp/20080220/xargs
トラックバックが送れなかったのでコメントしておきます。

#4 musha 2008/02/21 10:12

xargs -Jを使えばいいと思います。

#5 hnw 2008/02/21 14:55

hujikojpさん、mushaさん、ありがとうございます。xargsに-Jオプションなんてあるんですね。知りませんでした。いま調べたところ、FreeBSD/MacOSX標準には-Jが存在していました。ただ、残念ながらGNUのxargsには同等のオプションが見当たりませんでした。私はMacOSXが普段使いの端末ですので、GNU一色の生活を改めるチャンスなのかもしれません。

#6 matsuyama 2008/02/21 19:01

動作確認してない上あぶないですが、

% cat

#7 最新のファイルを一つxargsに渡す小ネタ 2008/03/25 12:18

[...] 先日、hnwがいまさらxargsの便利さを主張してみるの記事を投稿したところ予想外の大反響でした。xargs好きが意外といると判明したので今日はxargsを利用した小ネタをご紹介します。 [...]

#8 sodomojo 2009/10/10 01:12

ksh -cを利用すれば複数コマンドも実行できますよ。パイプも使えるので便利です。

find . -print0 | xargs -0 -i ksh -c “echo {} | wc -c && sleep 1″

コメントする