並列実行というと xargs
や GNU parallel
を思い出しますが、「エラーのあった実行のログを最後にまとめて出力する」ということを考えるとどちらも不十分でした。(できるのであれば教えてほしい)
しかし、シェルには wait
などの素敵な並列実行の仕組みが用意されているので、頑張って自分で実装してみました。
#!/bin/bash
#
# parallel.sh
# usage: echo 1 2 3 4 5 | parallel.sh my_script.sh
# 標準入力から受け取ったリストを引数として、引数で与えられたコマンドを実行する
# 成功したコマンドの結果を先に出力してから失敗したコマンドの結果を最後にまとめて出力する
list=$(cat)
tmpdir=$(mktemp -d)
for i in $list
do
($@ $i > $tmpdir/$i.log 2>&1; echo -n $? > $tmpdir/$i.status) &
done
wait
for i in $list
do
[ $(cat $tmpdir/$i.status) -eq 0 ] && cat $tmpdir/$i.log
done
for i in $list
do
[ $(cat $tmpdir/$i.status) -ne 0 ] && cat $tmpdir/$i.log && echo '*** error occurred!! ***'
done
code=0
for i in $list
do
status=$(cat $tmpdir/$i.status)
echo "status $status for command: $@ $i"
[ $status -ne 0 ] && code=$(expr $code + 1)
done
rm -r $tmpdir
exit $code
http://qiita.com/geta6/items/199faca823e84026c10a ←この記事を読めば解説は不要です。要は、すべてのコマンドをバックグラウンドで実行し、出力を $tmpdir/$i.log
に書いて、ステータスを $tmpdir/$i.status
に書いていて、あとは一個一個それらを処理しているだけです。
実験
引数が3の倍数のときだけ失敗するこのようなスクリプトを用意し、
#!/bin/bash
if [ $(expr $1 % 3) -eq 0 ]; then
echo $1
exit 1
else
echo $1
exit 0
fi
echo 1 2 3 4 5 6 7 8 9 | ./parallel.sh ./cmd.sh
のように実行すると、
1
2
4
5
7
8
3
*** error occurred!! ***
6
*** error occurred!! ***
9
*** error occurred!! ***
status 0 for command: ./cmd.sh 1
status 0 for command: ./cmd.sh 2
status 1 for command: ./cmd.sh 3
status 0 for command: ./cmd.sh 4
status 0 for command: ./cmd.sh 5
status 1 for command: ./cmd.sh 6
status 0 for command: ./cmd.sh 7
status 0 for command: ./cmd.sh 8
status 1 for command: ./cmd.sh 9
こうなるのがわかります。