並列バックグラウンドプロセス(サブシェル)の終了コードを収集する


18

次のようなbashスクリプトがあるとします。

echo "x" &
echo "y" &
echo "z" &
.....
echo "Z" &
wait

サブシェル/サブプロセスの終了コードを収集する方法はありますか?これを行う方法を探していますが、何も見つかりません。これらのサブシェルを並行して実行する必要があります。そうでなければ、これは簡単です。

汎用的なソリューションを探しています(並行して実行するサブプロセスの数が不明/動的です)。


1
私はあなたがあなたが何を望んでいるのかを理解してから、あなたが探している動作を正確に明らかにしようとして(おそらく擬似コードまたはより大きな例で)新しい質問をすることをお勧めします。
マイケルホーマー

3
私は実際に質問が今は良いと思う-私はサブプロセスの動的な数を持っています。すべての終了コードを収集する必要があります。それで全部です。
アレクサンダーミルズ

回答:


6

handleJobsを使用するAlexander Millsの回答は、素晴らしい出発点を与えてくれましたが、このエラーも与えてくれました。

警告:run_pending_traps:trap_list [17]の不正な値:0x461010

bashの競合状態の問題である可能性があります

代わりに、各子の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を実際にキャプチャするかどうかです。
アレクサンダーミルズ

1
「$ {1}」でテストしましたが、機能しません。関数に配列を渡していますが、明らかにbashでは特別な注意が必要です。$!は、最後に生成されたジョブのPIDです。tldp.org/ LDP / abs / html / internalvariables.htmlを参照してください。テストで正しく動作するようで、unRAIDのcache_dirsスクリプトで使用しています。その仕事。私はbash 4.4.12を使用しています。
アルバーグ

うんうん、あなたは正しいようです
アレクサンダーミルズ

20

waitPIDとともに使用ます。

各プロセス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 コマンドが完了するまで一時停止します(まだ完了していない場合)。

そのようなストールを回避したい場合はtraponSIGCHLD設定、終了の数をカウントし、すべてが終了waitしたときに一度にすべてを処理できます。waitほぼ常に単独で使用しても大丈夫でしょう。


1
ughh、申し訳ありませんが、私が問題になっている...ように指定しますが、並行してこれらのサブシェルを実行する必要があります
アレクサンダー・ミルズ

気にせず、多分これは私のセットアップで動作します...あなたのコードのどこで待機コマンドが機能しますか?私は従わない
アレクサンダーミルズ

1
@AlexanderMillsそれら並行し実行されています。それらの可変数がある場合、配列を使用します。(たとえば、ここでは重複している可能性があります)。
マイケルホーマー

はい、ありがとうございます。待機コマンドがあなたの答えに関係する場合は、チェックアウトします。それを追加してください
アレクサンダーミルズ

wait $X(合理的な)後の時点で実行します。
マイケルホーマー

5

コマンドを識別する適切な方法があれば、それらの終了コードを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ファイルを削除することを忘れないでください。


4

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

わかりました、これを一般化する方法はありますか?3つのサブプロセスだけでなく、Zサブプロセスもあります。
アレクサンダーミルズ

私は、一般的な解決策、感謝を探していますことを反映するために、元の質問を更新しました
アレクサンダー・ミルズ

それを生成する1つの方法は、ループ構造を使用することでしょうか?
アレクサンダーミルズ

ループ?コマンドの固定リストを持っていますか、それはユーザーによって制御されていますか?私はあなたが何をしようとしているのか理解していませんPIPESTATUSが、おそらくあなたがチェックアウトすべきものです。これseq 10 | gzip -c > seq.gz ; echo ${PIPESTATUS[@]}0 0(最初と最後のコマンドからの終了コード)を返します。
hschou

ええ、基本的にユーザーによって制御されます
アレクサンダーミルズ

2

これはあなたが探している一般的なスクリプトです。唯一の欠点は、コマンドが引用符で囲まれていることです。つまり、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の最良のアプローチです。


1
また、SIGCHLDトラップを使用して、その時点でのステータスの出力など、子が終了したときに子を処理することもできます。または進行状況カウンタを更新する:それはまた、おそらく「セット-m」として、非対話型シェルでオンにするオプションを必要とするかもしれません関数は、次に「トラップFUNCTION_NAMEのCHLDを」使用宣言
Chunko

1
また、「wait -n」は任意の子を待機し、その子の終了ステータスを$?で返します。変数。そのため、それぞれが終了するたびに進行状況を出力できます。ただし、CHLDトラップを使用しない限り、そのようにいくつかの子出口を見逃す可能性があることに注意してください。
春子

@Chunkoありがとう!それは良い情報です、あなたがおそらくあなたが最高だと思う何かで答えを更新できますか?
アレクサンダーミルズ

@Chunkoに感謝します。トラップはより良く機能します、あなたは正しいです。待機<pid>で、フォールスルーになりました。
アレクサンダーミルズ

トラップのあるバージョンが、トラップのないバージョンよりも優れていると考える理由と理由を説明できますか?(私はそれがより良くないと信じています、そして、それはそれがより悪いと思います、なぜならそれが利益なしでより複雑だからです。)
スコット

1

@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")"

前と同じようにファイルを作成しますが、一意のランダム文字列を追加します。

または、同じファイルにさらにステータス情報を追加できます。


1

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

AFAICT、これはマイケル・ホーマーの答えの再ハッシュに過ぎません。
スコット
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.