THE えのき

えのきです。

xargsって便利だなって改めて思った令和元年

この記事は CyberAgent Developers Advent Calendar 2019 - Adventar の19日目の記事です。紆余曲折あって28日に書いています。遅くなって申し訳ありません。
来年は期日どおりに記事公開することを目標にします。

ところで皆さんxargsってコマンドご存知でしょうか? ARG_MAX に合わせて引数調節してコマンド実行してくれるアレです。

今回は当然っちゃ当然な動きなんだけど、見方を変えたらますます便利なコマンドだなと改めて思ったという話を書きます。

環境

これからの話はAmazon Linux 2で動作を確認しています。

$ uname -r
4.14.138-114.102.amzn2.x86_64

$ xargs --version
xargs (GNU findutils) 4.5.11
Copyright (C) 2012 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Written by Eric B. Decker, James Youngman, and Kevin Dalley.

本題

手癖になってしまってるんですが、シェル(bash)で何かの入力を元に繰り返し処理を行う場合 for を使ったワンライナーをよく書きます。

例えばこんな感じのファイルがあるとします。

3
7
8
4
5
10
9
1
2
6

このファイルに1行ずつ何かしらの処理をしたいというときにはこんな感じのワンライナーをよく書きます。今回の処理は「受け取った数字分sleepしてその数字を出力する」ということにします。

$ for i in $(cat /tmp/awesome.txt); do sleep $i; echo $i; done
3
7
8
4
5
10
9
1
2
6

これはこれで動作確認しながらワンライナーを組み立てていけるので便利ですが、 並列で実行したい時に困る…!

ということで xargs の出番です。

まずは直列に実行をしてみます。 xargs には -P というオプションがあるんですが、これを指定しないとデフォルトの1が設定されるため、コマンドが直列で実行されます。 よくある xargs の使い方だと ls /path/to/dir | xargs rm みたいなものが多いですが、今回みたいに複数のコマンドの実行をしたいときにはbashなどを起動してコマンドを実行させる事もできます。

$ cat /tmp/awesome.txt | xargs -L 1 -I {} bash -c "sleep {}; echo {}"
3
7
8
4
5
10
9
1
2
6

次に -P を 3 に設定します。この場合だと、xargsがbashを最大3プロセス起動してコマンドを実行します。

$ cat /tmp/awesome.txt | xargs -L 1 -P 3 -I {} bash -c "sleep {}; echo {}"
3
7
4
8
5
1
2
9
10
6

順番が変わりましたね。次に10プロセス起動するようにしてみました。

$ cat /tmp/awesome.txt | xargs -L 1 -P 10 -I {} bash -c "sleep {}; echo {}"
1
2
3
4
5
6
7
8
9
10

ほぼ同時に10プロセス起動するので、数字が小さい順に出力されていますね。

記事の行数稼ぎのために実行時間を計測してみました。

直列 - for

直列なので55秒かかっています。

$ time for i in $(cat /tmp/awesome.txt); do sleep $i; echo $i; done
3
7
8
4
5
10
9
1
2
6

real    0m55.010s
user    0m0.009s
sys 0m0.001s

直列 - xargs

$ time cat /tmp/awesome.txt | xargs -L 1 -P 1 -I {} bash -c "sleep {}; echo {}"
3
7
8
4
5
10
9
1
2
6

real    0m55.019s
user    0m0.016s
sys 0m0.004s

並列 - xargs -P 10

10並列なので10秒で終わりましたね。

$ time cat /tmp/awesome.txt | xargs -L 1 -P 10 -I {} bash -c "sleep {}; echo {}"
1
2
3
4
5
6
7
8
9
10

real    0m10.005s
user    0m0.017s
sys 0m0.006s

bashのプロセスが10個起動しているので、その分マシンリソースを使います。

root      1179  0.0  0.1 160780  8840 ?        Ss   16:44   0:00  \_ sshd: enoki [priv]
enoki  1184  0.0  0.0 160780  4560 ?        S    16:44   0:00      \_ sshd: enoki@pts/1
enoki  1197  0.0  0.0 125024  4376 pts/1    Ss   16:44   0:00          \_ -bash
enoki 27028  0.0  0.0 115092  1868 pts/1    S+   17:23   0:00              \_ xargs -L 1 -P 10 -I {} bash -c sleep {}; echo {}
enoki 27029  0.0  0.0 119880  3000 pts/1    S+   17:23   0:00                  \_ bash -c sleep 3; echo 3
enoki 27034  0.0  0.0 114636   756 pts/1    S+   17:23   0:00                  |   \_ sleep 3
enoki 27030  0.0  0.0 119880  3000 pts/1    S+   17:23   0:00                  \_ bash -c sleep 7; echo 7
enoki 27035  0.0  0.0 114636   776 pts/1    S+   17:23   0:00                  |   \_ sleep 7
enoki 27031  0.0  0.0 119880  2904 pts/1    S+   17:23   0:00                  \_ bash -c sleep 8; echo 8
enoki 27037  0.0  0.0 114636   768 pts/1    S+   17:23   0:00                  |   \_ sleep 8
enoki 27032  0.0  0.0 119880  2936 pts/1    S+   17:23   0:00                  \_ bash -c sleep 4; echo 4
enoki 27038  0.0  0.0 114636   804 pts/1    S+   17:23   0:00                  |   \_ sleep 4
enoki 27033  0.0  0.0 119880  2860 pts/1    S+   17:23   0:00                  \_ bash -c sleep 5; echo 5
enoki 27040  0.0  0.0 114636   776 pts/1    S+   17:23   0:00                  |   \_ sleep 5
enoki 27036  0.0  0.0 119880  2908 pts/1    S+   17:23   0:00                  \_ bash -c sleep 10; echo 10
enoki 27043  0.0  0.0 114636   720 pts/1    S+   17:23   0:00                  |   \_ sleep 10
enoki 27039  0.0  0.0 119880  3000 pts/1    S+   17:23   0:00                  \_ bash -c sleep 9; echo 9
enoki 27045  0.0  0.0 114636   688 pts/1    S+   17:23   0:00                  |   \_ sleep 9
enoki 27041  0.0  0.0 119880  2884 pts/1    S+   17:23   0:00                  \_ bash -c sleep 1; echo 1
enoki 27046  0.0  0.0 114636   796 pts/1    S+   17:23   0:00                  |   \_ sleep 1
enoki 27042  0.0  0.0 119880  2892 pts/1    S+   17:23   0:00                  \_ bash -c sleep 2; echo 2
enoki 27047  0.0  0.0 114636   808 pts/1    S+   17:23   0:00                  |   \_ sleep 2
enoki 27044  0.0  0.0 119880  2956 pts/1    S+   17:23   0:00                  \_ bash -c sleep 6; echo 6
enoki 27048  0.0  0.0 114636   772 pts/1    S+   17:23   0:00                      \_ sleep 6

最後に

マシンリソースが少ない場合にxargsを使って並列化をするのはおすすめできません。 並列した数分のリソースが増えてしまうので、例えば本番環境のデータを何かしら処理したい場合は稼働中のサーバに影響を与えないように工夫してください。

他にもこういうのあるよ!っていうのがあればぜひコメントください!