Edited at

シェルスクリプトで多数のプログラムを同時実行: xargs, nohup, wait

サーバーで多数の実験を回すときにスクリプトである程度楽をするためのメモ.


xargs

xargsは, 標準入力から受け取ったものを引数とみなしたコマンドを生成して実行できる.

echo -e "1 2\n4 5\n7 8" | xargs -L 1 -P 2 seq



  • -L 数字: 数字行ずつ読み取って実行する


  • -P 数字: 同時に実行するプロセス数の指定

この例では, echoにより


1 2

4 5

7 8


が1行ずつxargsに入力される.

同時に実行するプロセス数を2と指定しているため, seq 1 2seq 4 5を同時実行したあと, seq 7 8が実行される.

もっと複雑なパラメータを試すには, ()forを囲んでechoするとよい.

(

for n in 10 20 30; do
for
d in `seq 5`; do
for
a in 1 0.1 0.01 0.001; do
echo $n $d $a
done
done
done

) | xargs -L 1 -P 2 ./my_experiment


xargsのリダイレクト

並列実行するプログラムそれぞれの標準出力をファイルに保存したい場合, 少しややこしくなる.

実行したいコマンドをスクリプトで覆う.


script.sh

#!/bin/sh

seq $1 $2 > seq_$1_$2

作成したスクリプトscript.shをxargsで呼ぶ.

echo -e "1 2\n4 5\n7 8" | xargs -L 1 -P 2 ./script.sh

これにより, seq_1_2, seq_4_5, seq_7_8の3つのファイルそれぞれに結果が書き込まれる.


nohup

並列実行するプログラムそれぞれの標準出力をファイルに保存したい場合, nohupを使う方法もある.

nohupコマンドはバックグラウンドでプログラムを動かすコマンドである.

nohup ./my_experiment 10 1 0.1 > result.txt &

このように打てば, 標準出力はresult.txtに記載され, ログアウトしたあとでも, プログラムは走り続ける. プログラムを終了したいときは,

pkill プロセス名kill プロセスID などを使う. (プロセスに関しては, top, ps, pgrep, lsofなどで確認. )

さて, 本題の並列実行は次のように行う.

#!/bin/sh

for n in 10 20 30; do
for
d in `seq 5`; do
for
a in 1 0.1 0.01 0.001; do
nohup ./my_experiment $n $d $a > "result${n}_${d}_${a}" &
done
done
done

上記の例では, nohupにより, プログラムの終了を待たずに全てのループが回る.

それぞれの引数に対する実験設定に関して, 別々の出力結果を保存できる.


プログラムの終了を同期する: wait

並列数をコントロールするにはwaitコマンドを使う.

wait プロセスID1 プロセスID2 ...

waitコマンドは指定したプロセスが終了するまで, 次の処理に進まない.

そのため, 終了を同期させたいときなどに使える.

#!/bin/bash

for n in 10 20 30; do
for
d in `seq 5`; do
array=()
for a in 1 0.1 0.01 0.001; do
nohup ./my_experiment $n $d $a > "result${n}_${d}_${a}" &
array+=($!)
done
wait ${array[@]}
done
done

先頭の#!/bin/sh#!/bin/bashに変わっていることに注意. (配列を使用するにはbashにする必要がある. )

$!変数には, 直前のプロセスIDが入るため, array配列は終了を合わせたいプロセスID達が保存されている.

この例では, それぞれのaに関してのみ同時実行され, 4つのaがすべて終わらなければ, 次のdへは進まない.


waitで監視できないプロセスの監視

waitコマンドは, 現在のシェルの子プロセスしか監視できない.


wait 3148

bash: wait: pid 3148 はこのシェルの子プロセスではありません


一度nohupでスクリプトを実行して, ログアウト後, そのスクリプトの終了を監視したいときは, 別の方法を取らなければならない.

#!/bin/bash

while true;do
pid=`pgrep script1.sh`
if test -z $pid ; then
echo "dead"
nohup ./script2.sh &
break
else
echo "running"
fi
sleep 5
done

この例では, script1.shというスクリプトの実行が終わり次第, script2.shを実行する.

監視間隔は5sである.



  • pgrep: プロセス名からプロセスIDを得るコマンド


  • test -z $pid: $pid文字列が空のとき真


まとめ


  • xargs


  • nohupforで同時実行


  • waitで終了同期


  • waitでできない場合, ループで監視