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を使って並列化をするのはおすすめできません。 並列した数分のリソースが増えてしまうので、例えば本番環境のデータを何かしら処理したい場合は稼働中のサーバに影響を与えないように工夫してください。
他にもこういうのあるよ!っていうのがあればぜひコメントください!