次のようなbashスクリプトがあるとします。
echo "x" &
echo "y" &
echo "z" &
.....
echo "Z" &
wait
サブシェル/サブプロセスの終了コードを収集する方法はありますか?これを行う方法を探していますが、何も見つかりません。これらのサブシェルを並行して実行する必要があります。そうでなければ、これは簡単です。
汎用的なソリューションを探しています(並行して実行するサブプロセスの数が不明/動的です)。
次のようなbashスクリプトがあるとします。
echo "x" &
echo "y" &
echo "z" &
.....
echo "Z" &
wait
サブシェル/サブプロセスの終了コードを収集する方法はありますか?これを行う方法を探していますが、何も見つかりません。これらのサブシェルを並行して実行する必要があります。そうでなければ、これは簡単です。
汎用的なソリューションを探しています(並行して実行するサブプロセスの数が不明/動的です)。
回答:
handleJobsを使用するAlexander Millsの回答は、素晴らしい出発点を与えてくれましたが、このエラーも与えてくれました。
警告:run_pending_traps:trap_list [17]の不正な値:0x461010
代わりに、各子のpidを保存して待機し、各子の終了コードを取得しました。このクリーナーは、サブプロセスが関数内にサブプロセスを生成し、子プロセスを待機するつもりの親プロセスを待機するリスクを回避するという点で見やすくなっています。トラップを使用しないため、何が起こるかがより明確になります。
#!/usr/bin/env bash
# it seems it does not work well if using echo for function return value, and calling inside $() (is a subprocess spawned?)
function wait_and_get_exit_codes() {
children=("$@")
EXIT_CODE=0
for job in "${children[@]}"; do
echo "PID => ${job}"
CODE=0;
wait ${job} || CODE=$?
if [[ "${CODE}" != "0" ]]; then
echo "At least one test failed with exit code => ${CODE}" ;
EXIT_CODE=1;
fi
done
}
DIRN=$(dirname "$0");
commands=(
"{ echo 'a'; exit 1; }"
"{ echo 'b'; exit 0; }"
"{ echo 'c'; exit 2; }"
)
clen=`expr "${#commands[@]}" - 1` # get length of commands - 1
children_pids=()
for i in `seq 0 "$clen"`; do
(echo "${commands[$i]}" | bash) & # run the command via bash in subshell
children_pids+=("$!")
echo "$i ith command has been issued as a background job"
done
# wait; # wait for all subshells to finish - its still valid to wait for all jobs to finish, before processing any exit-codes if we wanted to
#EXIT_CODE=0; # exit code of overall script
wait_and_get_exit_codes "${children_pids[@]}"
echo "EXIT_CODE => $EXIT_CODE"
exit "$EXIT_CODE"
# end
for job in "${childen[@]}"; do
あるべきfor job in "${1}"; do
明確にするため、けれども
children_pids+=("$!")
サブシェルに必要なpidを実際にキャプチャするかどうかです。
wait
PIDとともに使用します。
各プロセスID pidまたはジョブ仕様jobspecで指定された子プロセスが終了するまで待機し、最後に待機したコマンドの終了ステータスを返します。
進むにつれて、各プロセスのPIDを保存する必要があります。
echo "x" & X=$!
echo "y" & Y=$!
echo "z" & Z=$!
jobspec set -m
を使用して、スクリプトでジョブ制御を有効にして使用することもでき%n
ますが、ほとんどの場合はしたくないでしょう。ジョブ制御には他にも多くの副作用があります。
wait
プロセスが終了したのと同じコードを返します。あなたは使用することができるwait $X
よう、最終的なコードにアクセスするために、任意の(合理的)後の時点で$?
真/偽としてそれを単に使用します。
echo "x" & X=$!
echo "y" & Y=$!
...
wait $X
echo "job X returned $?"
wait
コマンドが完了するまで一時停止します(まだ完了していない場合)。
そのようなストールを回避したい場合は、trap
onSIGCHLD
を設定し、終了の数をカウントし、すべてが終了wait
したときに一度にすべてを処理できます。wait
ほぼ常に単独で使用しても大丈夫でしょう。
wait $X
(合理的な)後の時点で実行します。
compound command
-を使用して、ステートメントを括弧で囲みます。
( echo "x" ; echo X: $? ) &
( true ; echo TRUE: $? ) &
( false ; echo FALSE: $? ) &
出力を与えます
x
X: 0
TRUE: 0
FALSE: 1
複数のコマンドを並行して実行する本当に異なる方法は、GNU Parallelを使用することです。実行するコマンドのリストを作成し、それらをファイルに入れますlist
。
cat > list
sleep 2 ; exit 7
sleep 3 ; exit 55
^D
すべてのコマンドを並行して実行し、ファイル内の終了コードを収集しますjob.log
。
cat list | parallel -j0 --joblog job.log
cat job.log
出力は次のとおりです。
Seq Host Starttime JobRuntime Send Receive Exitval Signal Command
1 : 1486892487.325 1.976 0 0 7 0 sleep 2 ; exit 7
2 : 1486892487.326 3.003 0 0 55 0 sleep 3 ; exit 55
PIPESTATUS
が、おそらくあなたがチェックアウトすべきものです。これseq 10 | gzip -c > seq.gz ; echo ${PIPESTATUS[@]}
は0 0
(最初と最後のコマンドからの終了コード)を返します。
これはあなたが探している一般的なスクリプトです。唯一の欠点は、コマンドが引用符で囲まれていることです。つまり、IDEを介した構文の強調表示は実際には機能しません。そうでなければ、私は他のいくつかの答えを試してみましたが、これが最良の答えです。この答えにはwait <pid>
、@ Michaelによって与えられた使用のアイデアが組み込まれていますが、trap
最適に機能すると思われるコマンドを使用することで、さらに一歩進んでいます。
#!/usr/bin/env bash
set -m # allow for job control
EXIT_CODE=0; # exit code of overall script
function handleJobs() {
for job in `jobs -p`; do
echo "PID => ${job}"
CODE=0;
wait ${job} || CODE=$?
if [[ "${CODE}" != "0" ]]; then
echo "At least one test failed with exit code => ${CODE}" ;
EXIT_CODE=1;
fi
done
}
trap 'handleJobs' CHLD # trap command is the key part
DIRN=$(dirname "$0");
commands=(
"{ echo 'a'; exit 1; }"
"{ echo 'b'; exit 0; }"
"{ echo 'c'; exit 2; }"
)
clen=`expr "${#commands[@]}" - 1` # get length of commands - 1
for i in `seq 0 "$clen"`; do
(echo "${commands[$i]}" | bash) & # run the command via bash in subshell
echo "$i ith command has been issued as a background job"
done
wait; # wait for all subshells to finish
echo "EXIT_CODE => $EXIT_CODE"
exit "$EXIT_CODE"
# end
正しい道をtrap
歩んでくれた@michael homerに感謝しますが、コマンドを使用することがAFAICTの最良のアプローチです。
@rolfの答えの別のバリエーション:
終了ステータスを保存する別の方法は次のようなものです
mkdir /tmp/status_dir
そして、各スクリプトを持っています
script_name="${0##*/}" ## strip path from script name
tmpfile="/tmp/status_dir/${script_name}.$$"
do something
rc=$?
echo "$rc" > "$tmpfile"
これにより、ステータスファイルを作成したスクリプトの名前とそのプロセスID(同じスクリプトの複数のインスタンスが実行されている場合)を含む各ステータスファイルの一意の名前が与えられます。同じ場所なので、完了したらサブディレクトリ全体を削除できます。
次のようなことを行うことで、各スクリプトから複数のステータスを保存することもできます
tmpfile="$(/bin/mktemp -q "/tmp/status_dir/${script_name}.$$.XXXXXX")"
前と同じようにファイルを作成しますが、一意のランダム文字列を追加します。
または、同じファイルにさらにステータス情報を追加できます。
script3
場合にのみ実行されますscript1
し、script2
成功しているとscript1
し、script2
並列に実行されます。
./script1 &
process1=$!
./script2 &
process2=$!
wait $process1
rc1=$?
wait $process2
rc2=$?
if [[ $rc1 -eq 0 ]] && [[ $rc2 -eq 0 ]];then
./script3
fi