xargsは出力をコマンド引数に渡すだけのコマンドではない

最近まで、xargsは

「コマンドの出力結果を別のコマンドの引数に使う」

ためのコマンドだと思ってたんですけど、どうもそれだけのコマンドではないらしいのでその話。

xargsを引数を渡すために使う

一般的にxargsといえば、この使い方を連想する人が多いと思う。

し、実際にmanページでも

xargs -- construct argument list(s) and execute utility

「引数リストを構築してユーティリティ(多分これはコマンドと読み替えても問題ないはず)を実行する」とされています。

例えば、カレントディレクトリ以下にある「src」というディレクトリの中身を表示したい場合、 

$ find . -type d -name src
./fuga/src
./hoge/src
$ ls ./fuga/src ./hoge/src
./fuga/src:
fuga1.c fuga2.c fuga3.c

./hoge/src:
hoge1.c hoge2.c hoge3.c

findの細かい説明は省きますが、一度findで絞り込んでから、lsするという2段階を踏んでいます。(findで-exec使えよっていうツッコミはとりあえずなしで)

この例ではまだ2つしかsrcディレクトリが存在していないので大丈夫ですが、これが100個とかになってくると大変ですね。

xargsを使うと、任意のコマンドの出力を別のコマンドの引数に渡すことができるので、こういう処理も一行で書けます。

こんな感じで使います。

$ find . -type d -name src | xargs ls
./fuga/src:
fuga1.c fuga2.c fuga3.c

./hoge/src:
hoge1.c hoge2.c hoge3.c

これでワンライナーで実現できました。

xargsコマンドは、

[command 1] | xargs [command 2]

というふうにパイプで繋ぐと、[command 1]の出力が[command 2]の引数になります。

つまり今回の例では、 findコマンドで絞り込んだ結果をそのままlsの引数に渡されるので、

$ ls `find . -type d -name src | xargs ls`

とした場合と同じです(実はちょっと違うんだけどね)

 

xargsのLオプション、Iオプション

僕は今までは雑にxargsを使っていたので、特にオプションを知らなかったんですが、色々便利なオプションがあります。

その中でも比較的よく使いそうなLとIオプションの紹介をします。

[command1] | xargs -L [number] [command2]

xargsはデフォルトでは[command1]の出力を一気に全部引数にしてしまいますが、特定の数ずつ引数に渡したい場合があります。そういうときはLオプションを使うことで実現できます。

Lオプションは[command1]が[number]行出力するたびに[command2]を実行するオプションです。

これだけだとイメージが掴みづらいと思うので例を挙げると、

$ seq 10 | xargs echo
1 2 3 4 5 6 7 8 9 10

Lオプションを使わないとこんな感じになりますが、

$ seq 10 | xargs -L 2 echo
1 2
3 4
5 6
7 8
9 10

Lオプションで2を指定すると、引数が2つずつechoに渡されていることが分かります。

同様にLオプションで3を指定すると、

$ seq 10 | xargs -L 3 echo
1 2 3
4 5 6
7 8 9
10

3つずつ引数が渡されてます。

Lオプションで指定した数より出力が少ない場合は、読み込み終わっている出力を引数にしてコマンドを呼び出すので、最後の行は引数が1つしか入っていません。

 

[command1] | xargs -I [string] [command2] ... [string] ...

xargsで渡される引数はデフォルトでは一番最後にまとめて与えられますが、好きな順序で引数を渡したい場合があります。そういうときはIオプションを使うことで実現できます。

Iオプションは[command2]の引数中の[string]を、[command1]の出力に置き換えるオプションです。

$ ls
hoge_0 hoge_1 hoge_2 hoge_3

というディレクトリがあるとき、

$ ls | xargs -I FILE echo ファイル名はFILEです
ファイル名はhoge_0です
ファイル名はhoge_1です
ファイル名はhoge_2です
ファイル名はhoge_3です

echoの引数中のFILEという文字列をlsの出力に置換しています。

Iオプションを指定すると、自動的に-L 1が指定されるためこのような出力になっています。(詳しくは解説しませんが、-L 1以外にも-xも同時に指定されています)

 

xargsのPオプション

ここからが今日の本題です。

実は、xargsは並列実行にも使えます。

[command1] | xargs -P [number] [command2]

[number] に指定した数だけ[command2]を並列に実行します。

並列実行するには複数回[command2]を呼び出す必要があるので、事実上LオプションかIオプションのどちらか(または両方)を併用して使うことになります。

指定した秒数だけ待つsleepコマンドを使って並列実行を確認してみます。

$ time seq 5 | xargs -L 1 sleep

real 0m15.065s
user 0m0.011s
sys 0m0.025s

Pオプションを指定しない場合は、

$ sleep 1
$ sleep 2
$ sleep 3
$ sleep 4
$ sleep 5

とほぼ同じなので実行には当然15秒かかります。

次にPオプションを使って5プロセス並列実行してみます。

$ time seq 5 | xargs -P 5 -L 1 sleep

real 0m5.051s
user 0m0.013s
sys 0m0.028s

5つのsleepコマンドを並列に実行しているので5秒で終了します

また、[command2]を実行する回数が[number]を超えている場合、常に[number]だけプロセスを流すので、

$ time seq 5 | xargs -P 2 -L 1 sleep
real 0m9.045s user 0m0.012s sys 0m0.025s

となります。

図で書くとこのような順序で実行されています。

f:id:cameremon84:20180506001243p:plain

常に2つのコマンドが実行されていることが分かりますね。

 

まとめ

というわけで、並列実行にも使えちゃうxargsの話でした。

今回はsleepコマンドを使っていたので、ほぼ理論値の並列度を出せましたが、CPUリソースを食うようなコマンドとか、帯域を食うcurlとかwgetみたいなコマンドを使うと、当然ですが一定以上は実行速度は変わらなくなります。

あとLinuxBSDではちょいちょいオプションが違うらしいので注意してください。

Linux版かBSD版かは、適当にmanでも叩けば分かるはず。

とりあえず、両方のjmanを貼っておきます。

GNU Linux man → Man page of XARGS

Free BSD man → xargs(1) FreeBSDドキュメントJMan

xargs、奥が深いぜ。