
この記事は、株式会社カオナビ Advent Calendar 2021の8日目の記事です。
こんにちわ。貼り絵職人と申します。
最近はPython, Rubyなどが使われてるシェルスクリプトですが
古のインターネットおじさんなで提供はbashにしようかとおもいます。
bashで並列化をするときの方法
今回のお話はbashを並列する方法です。プログラミング、特にサーバーサイドの処理はシングルスレッドが多いですが、Native開発、サーバー側のコアな部分での大規模データの分散処理など
そういう時に並列処理を使います
bashで並列処理をするにはおおまかに2つの方法があります
- xargs方法
- &方法
それぞれ特徴があります
実装方法は違えどどちらも可能です(若干ですが可読性は&のほうがよい)
xargs
- ワンライナーで簡素にできる
- 高速である
などの特徴があります
xargsで利用する並列の例
たとえばこういうシェルスクリプトがあります
#!/bin/zsh
echo "catch $1"
# 実行時間 待機
sleep 1
echo "hello $1"
単に1秒待って引数を表示させるshellです。これを連続で実行しましょう。
$seq 5 | xargs -I{} sh ./echo.sh {}
seqで1〜5の数値をパイプから渡し、xargs の -Iオプションで変数{}に渡し、echo.shで実行します

直列実行だと、1~5と順番に動作します
引数が1~5なので短く感じますがこれが1万、10万になると時間が膨大になります
なので、プロセスを並列する-Pオプションを入れてみましょう
$seq 5 | xargs -P5 -I{} sh ./echo.sh {}
-Iは変数として{}を宣言します。
-Pオプションは並列させるプロセス数となります。これで実行してみましょう

5プロセス同時に実行しているので1秒待ってhelloが出る形です。
実行時間の短縮にもなります
xargsでちょっと複雑な並列処理
少し複雑な処理をしてみましょう
例えば以下のような要件があります

①mysqlサーバーが7台あり、それを並列実行したいです
②さらにmysqlにわたすパラメータは複数となります

サーバーは7台あると見込んで作ってみましょう
echo.shに複数パラメータが渡せるように修正しましょう
#!/bin/bash
echo "catch $1"
# 実行時間 待機
sleep 1
echo "hello $1:$2"
# 実行時間 待機
sleep 1
echo "var $1:$4"
# 実行時間 待機
sleep 1
echo "ここでmysqlサーバーのクエリ実行 $1: $2: $3: $4"
複数のmysql
(mysqlサーバーを立てたり、どういうテーブルで実行するかなど詳細は割愛します)
とりいそぎ、検索結果をhost.txt という形に変換しておきます
# host=`MYSQL_PWD="password" mysql -uroot -hroot common -N -s -e "select * from host"`
# ↑の検索結果としてhost.txtを代替
#$ cat host.txt
host01
host02
host03
host04
host05
host06
host07
array型に変換してどの様になってるか確かめてみましょう
#$ array_text=(`cat host.txt`)| echo ${array_text[@]}
host01 host02 host03 host04 host05 host06 host07
これを並列実行してみます
array_text=(`cat host.txt`)| echo ${array_text[@]} | xargs -L1 -P5 -I{} bash -c "./echo2.sh {} aaa bbb ccc"
array_textはhost.txtを配列化します
bashではecho ${array_text[@]}することで
- 変数の宣言
- xargsにわたす
役割があります。xargsのオプションは以下になります
-I | xargsで利用する変数 |
-n | echo2.shにわたす引数の数 |
-P | 並列するプロセス数 |
bash -c | bashとして実行 |

実行可能できました。ワンライナーでスッキリしますが、可読性は悪いですね
&
もう一つの方法として実行に&を後ろにつけてwaitを入れることで並列化ができます
メリットとしては「可読性が良い」という点があります
xargsのオプションをしらないエンジニアでもなんとなくわかりますね.
見やすくするため、forで回すシェルスクリプトを作ります
#!/bin/bash
array_text=(`cat host.txt`)
#並列処理
for host in $(echo "${array_text[@]}"); do
bash -c "./echo2 ${host} aaa bbb ccc" &
done
wait

結論
実際Blogを書くまで実は「xargsは複雑な処理はできない」と思っておりましたが、実際検証してみるとどちらも並列化できることがわかりました。bashの世界は奥が深い