UNIX/Linuxの部屋 コマンド:xargs

TOP UNIX/Linuxの部屋 UNIX/Linuxコマンド一覧 用語集 新版 由来/読み方辞書 環境変数マニュアル Cシェル変数 システム設定ファイル システムコール・ライブラリ ネットワークプログラミングの基礎知識 クラウドサービス徹底比較・徹底解説




コマンド xargs 標準入力から引数を読み込み、指定のコマンドを実行する。並列実行で高速化する。 このエントリーをはてなブックマークに追加

最終更新


UNIX/Linux の xargs コマンドは、標準入力から受け取った文字列を、コマンドの引数として指定できるコマンドである。コマンドライン文字列が長すぎるエラーを回避に使用する。また、xargs の実装によっては並列実行して処理を高速化させることができる。


xargs コマンドの基本的な使い方
xargs コマンドの仕組みは後から説明するとして、xargs コマンドのよくある使い方を説明する。

▷ カレントディレクトリ以下のファイルについて、hoge を含む文字列を grep する。
% find . -type f -print | xargs grep hoge /dev/null

▷ カレントディレクトリ以下のファイルについて、/tmp に mv する。
% find . -type f -print | xargs echo mv -t /tmp
→ Linux などで使われる GNU coreutils の mv の場合 (-t オプションが使える)
% find . -type f -print | xargs -i{} -n 10 echo mv {} /tmp
→ Linux などで使われる GNU findutils の xargs の場合
% find . -type f -print | xargs -n1 -J % echo mv % /tmp
→ FreeBSD の場合
※動作を確認できるよう echo 文を入れてあるので、実際に実行する際は echo を外すこと。

▷ gzip による圧縮を 5並列で行う。
% find . -type f -print | xargs -n1 -P5 echo gzip {}
※動作を確認できるよう echo 文を入れてあるので、実際に実行する際は echo を外すこと。

▷ rm によるファイル削除を 5並列で行う。
% find . -type f -print | xargs -n1 -P5 echo rm {}
※動作を確認できるよう echo 文を入れてあるので、実際に実行する際は echo を外すこと。

xargs コマンドのオプション
xargs コマンドのオプションを下記に示す。なお、--XXXX 形式のロングオプションを受け付けるのは、主に Linux で使われる GNU findutils に含まれる xargs のみである。

▷ -p または --interactive
実行するコマンドを表示し、y か Y を入力したときのみ実際に実行する。
▷ -t または --verbose
実行するコマンドを標準エラー出力に出力する。

▷ -0 または --null
入力の区切りが \0 であるものとして処理する。find -print0 と組み合わせて使うのが一般的。
% find . -type f -print0 | xargs -0 grep hoge /dev/null
▷ -L [行数] または --max-lines=[行数]
標準入力から [行数] を受け取るたびに、1回コマンドを実行する。
▷ -n [文字列数] または --max-agrs=[文字列数]
標準入力から [文字列数] を受け取るたびに、1回コマンドを実行する。
▷ -i [文字列] または --replace=[文字列]
コマンドライン引数の [文字列] の部分を、標準入力から受け取った文字列に置換する。-i を指定すると、-l 1 が指定されたものとみなす。
▷ -J [文字列]
BSD 系のみ。BSD の xargs には -i が使えない。代わりに -J を使う。-i とは異なり、-l 1 が暗黙には指定されないことに注意。

xargs コマンドの仕組み
xargs コマンドは標準入力の複数の行を、コマンドライン文字列に変換した上で任意のコマンドを実行するコマンドである。と言っても意味が全くわからないと思うので、例で説明する。

下記は echo を 3つつなげて、3行の出力を行っている。
% echo aa; echo bb; echo cc
aa
bb
cc
これを xargs につなげてみる。その際、引数に実行したいコマンド名を付ける。ここでは echo コマンドをつけてみよう。すると下記のようになった。
% (echo aa; echo bb; echo cc) | xargs echo
aa bb cc

何が起こっているかわかりやすくするため、-t オプションで xargs がどのようなコマンドを実行しているか表示させてみよう。
% (echo aa; echo bb; echo cc) | xargs -t echo
echo aa bb cc
aa bb cc
xargs は "echo aa bb cc" というコマンドを実行していることがわかる。

つまり、
xargs [コマンド]
は、標準入力から受け取った複数行の
aa
bb
cc
を、改行コードを削除して 1行にし、下記のように実行してくれるコマンドなのである。
[コマンド] aa bb cc

典型的な使い方としては、下記のように find でファイルを探し、それに対して grep で検索する、というものがある。
% find . -type f | xargs grep hoge

xargs コマンドと ARG_MAX
「それって下記でも同じではないの?」と思うかもしれない。
% grep hoge `find . -type f`

しかしながら xargs はコマンドライン引数の最大値 ARG_MAX を見るという機能がある。

grep や ls などで、
% grep hoge /home/user/*
などとファイルグロブを指定したとき、ファイル数が多すぎると
  • Argument list too long.
  • Arg list too long.
  • 引数が多すぎます
というエラーになることがある。これは、シェルが展開した文字列が ARG_MAX という OS のコマンドラインの長さ制限を越えたためである。/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) がエラーとするわけである。

一方、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)
コマンドラインの長さの制限は下記のように OS によって異なる。
  • FreeBSD は 10-RELEASE 前後で 256KB (それ以前は 64KB)
  • Linux の CentOS では 256KB、Debian では 2MB
  • SunOS4〜Solaris は 1MB
  • HP-UX 10.20 までは 20KB (パッチを当てれば 2MB)、HP-UX 11.x 以降は 2MB
1994年の 4.4BSD では 20KB なので、古代 UNIX では 20KB 程度だったのかもしれないが、その後扱うデータが増えるにつれ、利便性向上のために各 OS で少しずつ引き上げられているようだ。

この値は伝統的に ARG_MAX というマクロ定数で定義されているため、使っている UNIX/Linux の ARG_MAX を調べたい場合は /usr/include の下を ARG_MAX で grep するか、下記のように getconf コマンドを使うことで調べることができる。
% getconf ARG_MAX
262144

FreeBSD なら sysctl で
% sysctl -A kern.argmax
kern.argmax: 262144
としてもよい。なお、ARG_MAX は 4096 以上であるべき、と POSIX には規定されている。

シェル内部コマンドと ARG_MAX
ARG_MAX の制限はあくまでシステムコール execve(2) に渡す文字列長の制限であり、シェル内部コマンドはその制限を受けない。例えば ARG_MAX が 256KB である FreeBSD において、下記コマンドは当ページ管理人の環境では 1.5MB 程度になるが、問題なく実行できる。
% echo /* /*/* /*/*/* /*/*/*/* | xargs hoge

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

Tips.1 /dev/null
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 find + xargs
ソースの中から文字列「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 より遅い。
>> Solaris10オンラインマニュアル(man) Solaris10 xargs(1)
>> FreeBSDオンラインマニュアル(man) FreeBSD xargs(1)
>> Linuxオンラインマニュアル(man) Linux xargs(1)

読み方 xargs (UNIXコマンド) [えっくす・あーぐす] このエントリーをはてなブックマークに追加

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