次のようなbashスクリプトがあるとします。
echo "x" &
echo "y" &
echo "z" &
.....
echo "Z" &
wait
サブシェル/サブプロセスの終了コードを収集する方法はありますか?これを行う方法を探していて、何も見つからない。 これらのサブシェルを並行して実行する必要があります。そうでない場合は、これが簡単になります。
genericソリューションを探しています(並行して実行するサブプロセスの数が不明/動的である)。
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
PIDとともに wait
を使用します は次のようになります:
各プロセスIDで指定された子プロセスpidまたはジョブ仕様jobspec終了し、最後に待機したコマンドの終了ステータスを返します。
進行中に、各プロセスのPIDを保存する必要があります。
echo "x" & X=$!
echo "y" & Y=$!
echo "z" & Z=$!
set -m
を使用してスクリプトでジョブ制御を有効にし、%n
ジョブ仕様を使用することもできますが、ほとんどの場合、したくない- ジョブ制御には他の多くの副作用があります =。
wait
は、プロセスが終了したときと同じコードを返します。後で(合理的な)任意の時点でwait $X
を使用して、$?
として最終コードにアクセスするか、単にtrue/falseとして使用できます。
echo "x" & X=$!
echo "y" & Y=$!
...
wait $X
echo "job X returned $?"
wait
は、コマンドが完了するまで、コマンドが完了するまで一時停止します。
そのような停止を回避したい場合は、 trap
をSIGCHLD
に設定 、終了の数を数え、すべてのwait
sを処理できます。彼らがすべて終わったらすぐに。おそらく、ほとんどいつでもwait
を単独で使用することで問題を回避できます。
コマンドを特定する良い方法がある場合は、その終了コードをtmpファイルに出力してから、目的の特定のファイルにアクセスできます。
#!/bin/bash
for i in `seq 1 5`; do
( sleep $i ; echo $? > /tmp/cmd__${i} ) &
done
wait
for i in `seq 1 5`; do # or even /tmp/cmd__*
echo "process $i:"
cat /tmp/cmd__${i}
done
Tmpファイルを削除することを忘れないでください。
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
これは、探している一般的なスクリプトです。唯一の欠点は、コマンドが引用符で囲まれていることです。つまり、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
正しい軌道に乗ってくれた@michael homerに感謝しますが、trap
コマンドを使用することが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