バックグラウンドプロセスの信頼できるリターンコード


12

次のbashコードを想定してみましょう。

foo > logfile 2>&1 &
foo_pid=$!

while ps -p$foo_pid
do
    ping -c 1 localhost
done

wait $foo_pid

if [[ $? == 0 ]]
then
    echo "foo success"
fi

$?の戻りコードではfooなく、の戻りコードが実際に含まれていると想定しても安全pingですか?その質問に対する答えが「あなたはそれを仮定することはできません」である場合。次に、このコードを変更して、$?常に戻りコードが含まれていることを確認するにはどうすればよいfooですか?

回答:


12

ではbash、あなたは別のバックグラウンドジョブを開始していない限り、その保証を持っている(とバックグラウンドジョブが使用を開始することができることを注意します&が、またしてcoprocプロセス置換とと)の間foo &wait

POSIXでは、シェルが終了した後、少なくとも25個のジョブの終了ステータスをシェルが記憶する必要がありますがbashそれ以上のことは記憶しています。

今、あなたがするなら:

foo & pid=$!
...
bar &
wait "$pid"

(開始時刻までに終了した場合)barと同じpidが与えられないという保証はありません。そのため、可能性は低いものの、終了ステータスが表示される場合があります。foofoobarwait "$pid"bar

次の方法で再現できます。

bash -c '(exit 12; foo) & pid=$!
         while : bar & [ "$pid" != "$!" ]; do :;done
         wait "$pid"; echo "$?"'

(最終的に)の0代わりに表示され12ます。

問題を回避するための1つの方法は、次のように記述することです。

{
  foo_pid=$!

  while ps -p "$foo_pid"
  do
      ping -c 1 localhost
  done

  bar &
  ...

  read <&3 ret
  if [ "$ret" = 0 ]; then
    echo foo was sucessful.
  fi
} 3< <(foo > logfile 2>&1; echo "$?")

4

はい、wait "$!"バックグラウンドジョブのステータスを取得することができます。スクリプトとして実行する場合、bashは完了したバックグラウンドジョブを自動的に収集しません。したがって、実行するとwaitwait呼び出された時点でジョブを収集します。

簡単なスクリプトでこれをテストできます:

#!/bin/bash
sh -c 'sleep 1; exit 22' &
sleep 5
echo "FG: $?"
wait %1
echo "BG: $?"

出力されるもの:

FG: 0
BG: 22

そのステートメントの重要な部分は「スクリプトとして実行するとき」の始まりでした。対話型の場合、wait機能しません。プロセスが収集され、プロンプトが表示される直前に終了ステータスが破棄されます(デフォルト)。
パトリック14

bash 4.2.37、4.1.2、および3.2.48で試しました。それらはすべてまったく同じように動作します(回答のコードのリテラルコピー/貼り付け)。wait %1バックグラウンド・プロセスは、「スリープ5」が完了した直後に収集される「いいえ、そのような仕事」で失敗します。
パトリック14

ああ、すみません、今すぐ手に入れました。の%1代わりにあなたを逃していました$!
ステファンシャゼル14

bash -c '(sleep 1;exit 5) & sleep 2; wait %1; echo $?'(非対話型でも)そのデッドジョブの終了ステータスの取得に失敗することに注意してください。バグのように聞こえます。
ステファンシャゼラス14

0

あなたの仮定は正しいと思います。man bashバックグラウンドプロセスの待機に関する抜粋を次に示します。

nが存在しないプロセスまたはジョブを指定する場合、戻りステータスは127です。それ以外の場合、戻りステータスは最後に待機したプロセスまたはジョブの終了ステータスです。

だから、おそらく127をチェックする必要があります

役立つかもしれないものとはまったく異なる答えを持つ同様の質問があります。

Bashスクリプトはプロセスを待機し、戻りコードを取得します

編集1

@Stephaneのコメントと回答に触発されて、彼のスクリプトを拡張しました。追跡を開始する前に、約34のバックグラウンドプロセスを開始できます。

tback

$ cat tback 
plist=()
elist=()
slist=([1]=12 [2]=15 [3]=17 [4]=19 [5]=21 [6]=23)
count=30

#start background tasksto monitor
for i in 1 2 3 4
do
  #echo pid $i ${plist[$i]} ${slist[$i]}
  (echo $BASHPID-${slist[$i]} running; exit ${slist[$i]}) & 
  plist[$i]=$!
done

echo starting $count background echos to test history
for i in `eval echo {1..$count}`
do
  echo -n "." &
  elist[$i]=$! 
done
# wait for each background echo to complete
for i in `eval echo {1..$count}`
do
  wait ${elist[$i]}
  echo -n $? 
done
echo ""
# Now wait for each monitored process and check return status with expected
failed=0
for i in 1 2 3 4
do
  wait ${plist[$i]}
  rv=$?
  echo " pid ${plist[$i]} returns $rv should be ${slist[$i]}"
  if [[ $rv != ${slist[$i]} ]] 
  then
    failed=1
  fi
done

wait
echo "Complete $failed"
if [[ $failed = "1" ]]
then
  echo Failed
else
  echo Success
fi
exit $failed
$ 

私のシステムでは

$ bash tback
14553-12 running
14554-15 running
14555-17 running
starting 30 background echos to test history
14556-19 running
..............................000000000000000000000000000000
 pid 14553 returns 12 should be 12
 pid 14554 returns 15 should be 15
 pid 14555 returns 17 should be 17
 pid 14556 returns 19 should be 19
Complete 0
Success

1
いいえ、私の見ウムラウトの答えにコメントを、として自分自身でそれを試してみてくださいbash -c '(exit 12) & sleep 1; wait "$!"; echo "$?"'
ステファンChazelas

bash数千のジョブを開始した後でも、ゆるい軌道を見たことはありません。私の例は、pidが再利用されていることを示していました。
ステファンシャゼル14
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.