UNIXの部屋 コマンド検索: xargs


※空白区切りで AND 検索 (例:「ファイル 削除」)

xargs 標準入力から引数を読み込み、指定のコマンドを実行する

grep や ls などで、
% grep hoge /home/user/*
などとファイルグロブを指定したとき、ファイル数が多すぎると
  • Argument list too long.
  • Arg list too long.
  • 引数が多すぎます
というエラーになることがある。これは、シェルが展開した文字列が OS の制限 (正確には execve(2) の制限) を越えたためである。/home/user/ の下に
a00001, a00002, a00003 …
というファイルが存在した場合、シェルは
grep(\0)hoge(\0)/home/user/a00001(\0)/home/user/a00002(\0)/home/user/a00003(\0)…
という文字列を生成し、execve(2) に渡す。この文字列が長すぎた場合、execve(2) がエラーとするわけである。文字列が短ければよいわけなので、
% cd /home/user
% grep hoge *
とカレントディレクトリを変更してから実行すれば、
grep(\0)hoge(\0)a00001(\0)a00002(\0)a00003(\0)…
と文字列が短くなり、execve(2) の制限を回避できる (かもしれない)。

長さの制限は OS によって異なり、FreeBSD では 64KB、Linux のあるディストリビューションでは 128KB、SunOS4〜Solaris は 1MB、HP-UX 10.20 までは 20KB (パッチを当てれば 2MB)、HP-UX 11.x 以降は 2MB である。この値は伝統的に ARG_MAX というマクロ定数で定義されているため、使っている UNIX の ARG_MAX を調べたい場合は /usr/include を ARG_MAX で grep するか、getconf コマンドを使って
% getconf ARG_MAX
65536
とすることで調べることができる。FreeBSD なら sysctl で
% sysctl -A kern.argmax
kern.argmax: 65536
としてもよい。なお、ARG_MAX は 4096 以上であるべき、と POSIX には規定されている。

この ARG_MAX の制限を回避するには echo と xargs を使う。
% echo /home/user/* | xargs grep hoge
echo はファイル名の一覧を標準出力に出力する。それをパイプ経由で xargs が標準入力から受け取り、
% grep hoge /home/user/a00001 /home/user/a00002 /home/user/a00003 …
というコマンドを実行する。echo はシェルの内部コマンドのため ARG_MAX の制限を受けない。一方、xargs は ARG_MAX の値を把握していて、引数が ARG_MAX を越えそうになると
% grep hoge /home/user/a00001 /home/user/a00002 … /home/user/a00943
% grep hoge /home/user/a00944 /home/user/a00945 … /home/user/a01886
% grep hoge /home/user/a01887 /home/user/a01888 … /home/user/a02829
と複数回に分けて実行してくれる。

なお、実際は ARG_MAX は、
「実行するコマンド名 + 引数 + 子プロセスに渡す環境変数」の長さ
の上限であるため、あまりに長い環境変数を設定していると、その分指定できる引数が減ってしまう。

Tips.1
grep の対象として
% echo /home/user/* | xargs grep hoge /dev/null
と /dev/null を指定しておくとよい。これは、例えば /home/user/a00001〜a00944 が存在したとして
% echo /home/user/* | xargs grep hoge
とした場合、
% grep hoge /home/user/a00001 /home/user/a00002 … /home/user/a00943
% grep hoge /home/user/a00944
が実行される。grep は複数のファイルから検索する場合は行頭にファイル名を表示するが、検索対象ファイルが 1つしかない場合はファイル名を表示しない。よって、もし a00944 の中に検索対象文字列が存在した場合、
/home/user/a00001: hogehoge
/home/user/a00013: hogehoge
hogehoge
とファイル名が表示されず、わかりづらい。xargs の末尾に /dev/null を指定することで
% grep hoge /dev/null /home/user/a00001 /home/user/a00002 … /home/user/a00943
% grep hoge /dev/null /home/user/a00944
となり、必ず複数ファイルから検索されるようになる。なお、/dev/null は常にゼロバイトのファイルなので、検索結果には影響を与えない。

Tips.2
ソースの中から文字列「hogehoge」を検索するには find を使って
% find . -name \*.c grep hogehoge {} \;
としてもよいが、*.c のファイルの数だけ grep コマンドが実行され、時間がかかってしまう。そういう場合に xargs を使うと時間短縮の効果がある。

もしファイル名の長さが ARG_MAX 以下に収まった場合、
% find . -name \*.c -print | xargs grep hogehoge
とすると grep は一度しか実行されないため、
% find . -name \*.c grep hogehoge {} \;
より速いということになる。実際、FreeBSD 3.3-RELEASE で、/usr/src/sys/ 以下の *.c に対して、それぞれの方法で grep abc したところ、
  • find から grep を実行したものが 14.55 秒
  • xargs を使ったものが 3.63 秒
となり、xargs の方が4倍近く速かった。

しかし、xargs は入力文字列が ARG_MAX を越えるまで grep を実行しようとしないので、最初の grep の結果が表示されるまで時間がかかる。よって、体感的にはかえって遅くなったと思う場合があるかもしれない (ただし実際には xargs によって遅くなることはない)。そういう場合は、xargs に -n オプションを付けるとよい。例えば
% find . -name \*.c -print | xargs -n 10 grep hogehoge
とすると、find が 10ファイル分を出力するたびに grep が実行されることになる。ちなみにこの時間は 6.08 秒で、当然ながら find+grep より速いが、-n オプション抜きの xargs より遅い。
>> コマンド find *
>> 読み方 xargs *

xargs (UNIXコマンド) [えっくす・あーぐす]

マニュアルなどには記載はないが、おそらく "eXecute ARGumentS" あたりが由来では。PWB UNIX で出現したコマンド。
>> コマンド xargs *