web-dev-qa-db-ja.com

いくつかのサブプロセスが終了するまでbashで待機し、サブプロセスがcode!= 0で終了したときに終了コード!= 0を返す方法を教えてください。

Bashスクリプト内で、そのスクリプトから生成されたいくつかのサブプロセスが終了して終了コード!= 0を返すまで待機する方法

簡単なスクリプト:

#!/bin/bash
for i in `seq 0 9`; do
  doCalculations $i &
done
wait

上記のスクリプトは、生成された10個すべてのサブプロセスを待ちますが、常に終了ステータス0を返します(help waitを参照)。このスクリプトを変更して、生成されたサブプロセスの終了ステータスを検出し、サブプロセスのいずれかがcode!= 0で終了したときに終了コード1を返すようにするにはどうすればよいですか。

サブプロセスのPIDを収集し、順番に待機して終了ステータスを合計するよりも良い解決策はありますか?

469
tkokoszka

wait また、(オプションで)待機するプロセスのPIDを取得し、$を使用します。バックグラウンドで起動された最後のコマンドのPIDを取得します。生成された各サブプロセスのPIDを配列に格納するようにループを変更してから、各PIDを待って再度ループします。

# run processes and store pids in array
for i in $n_procs; do
    ./procs[${i}] &
    pids[${i}]=$!
done

# wait for all pids
for pid in ${pids[*]}; do
    wait $pid
done
428
Luca Tettamanti

http://jeremy.zawodny.com/blog/archives/010717.html

#!/bin/bash

FAIL=0

echo "starting"

./sleeper 2 0 &
./sleeper 2 1 &
./sleeper 3 0 &
./sleeper 2 0 &

for job in `jobs -p`
do
echo $job
    wait $job || let "FAIL+=1"
done

echo $FAIL

if [ "$FAIL" == "0" ];
then
echo "YAY!"
else
echo "FAIL! ($FAIL)"
fi
268
HoverHell

GNU Parallelがインストールされていれば、次のことができます。

# If doCalculations is a function
export -f doCalculations
seq 0 9 | parallel doCalculations {}

GNU Parallelはあなたに終了コードを与えるでしょう:

  • 0 - すべてのジョブがエラーなしで実行されました。

  • 1-253 - 一部のジョブが失敗しました。終了状況は失敗したジョブの数を示します

  • 254 - 253を超えるジョブが失敗しました。

  • 255 - その他のエラー.

詳細についてはイントロビデオを見てください: http://pi.dk/1

45
Ole Tange

これが私がこれまでに思いついたものです。 WAITALL_DELAYを自分の用法に合わせる必要がないように、子供が終了したらsleepコマンドを中断する方法を見たいと思います。

waitall() { # PID...
  ## Wait for children to exit and indicate whether all exited with 0 status.
  local errors=0
  while :; do
    debug "Processes remaining: $*"
    for pid in "$@"; do
      shift
      if kill -0 "$pid" 2>/dev/null; then
        debug "$pid is still alive."
        set -- "$@" "$pid"
      Elif wait "$pid"; then
        debug "$pid exited with zero exit status."
      else
        debug "$pid exited with non-zero exit status."
        ((++errors))
      fi
    done
    (("$#" > 0)) || break
    # TODO: how to interrupt this sleep when a child terminates?
    sleep ${WAITALL_DELAY:-1}
   done
  ((errors == 0))
}

debug() { echo "DEBUG: $*" >&2; }

pids=""
for t in 3 5 4; do 
  sleep "$t" &
  pids="$pids $!"
done
waitall $pids
39
Mark Edgar

単純にどうですか。

#!/bin/bash

pids=""

for i in `seq 0 9`; do
   doCalculations $i &
   pids="$pids $!"
done

wait $pids

...code continued here ...

更新:

複数のコメント投稿者が指摘したように、上記はすべてのプロセスが完了するのを待って続行しますが、いずれかが失敗しても終了せず失敗します。@Bryan、@SamBrightmanなどが提案する次の変更で実行できます。 :

#!/bin/bash

pids=""
RESULT=0


for i in `seq 0 9`; do
   doCalculations $i &
   pids="$pids $!"
done

for pid in $pids; do
    wait $pid || let "RESULT=1"
done

if [ "$RESULT" == "1" ];
    then
       exit 1
fi

...code continued here ...
36
patapouf_ai

これはwaitを使った簡単な例です。

いくつかのプロセスを実行します。

$ sleep 10 &
$ sleep 10 &
$ sleep 20 &
$ sleep 20 &

それからwaitコマンドでそれらを待ちます:

$ wait < <(jobs -p)

あるいはすべての人に(引数なしで)単にwaitを渡してください。

これにより、バックグラウンドですべてのジョブが完了するのを待ちます。

-nオプションが指定されている場合は、次のジョブが終了するのを待って終了ステータスを返します。

構文についてはhelp waithelp jobsを参照してください。

ただし、欠点は最後のIDのステータスのみが返されるため、各サブプロセスのステータスを確認して変数に格納する必要があることです。

または、失敗したときにファイルを作成するように計算機能を作成し(空または失敗ログ付き)、存在する場合はそのファイルを確認します。

$ sleep 20 && true || tee fail &
$ sleep 20 && false || tee fail &
$ wait < <(jobs -p)
$ test -f fail && echo Calculation failed.
30
kenorb

これを並列化するには.

for i in $(whatever_list) ; do
   do_something $i
done

これに翻訳する...

for i in $(whatever_list) ; do echo $i ; done | ## execute in parallel...
   (
   export -f do_something ## export functions (if needed)
   export PATH ## export any variables that are required
   xargs -I{} --max-procs 0 bash -c ' ## process in batches...
      {
      echo "processing {}" ## optional
      do_something {}
      }' 
   )
  • あるプロセスでエラーが発生しても 他のプロセスに割り込むことはありませんが、 シーケンス全体からゼロ以外の終了コードになります
  • 関数や変数をエクスポートすることは、どんな場合でも必要かもしれません。
  • --max-procsは、どれだけの並列処理が必要かに基づいて設定できます(0は「一度に全部」を意味します)。
  • GNU Parallelxargsの代わりに使用すると、いくつかの追加機能が提供されます - ただし、デフォルトでインストールされるとは限りません。
  • forループは、この例では厳密には必要ありません。echo $iは基本的に単に$(whatever_listの出力を再生成するためです。 forキーワードを使用すると、何が起こっているのかが少しわかりやすくなります。
  • Bash文字列の処理は混乱を招く可能性があります - 私は一重引用符を使用することが重要なスクリプトをラップするのに最適であることを発見しました。
  • あなたは簡単に全体の操作を中断することができます(^ Cか類似のものを使って)、 Bash並列処理へのより直接的なアプローチとは異なり

これが簡単な作業例です。

for i in {0..5} ; do echo $i ; done |xargs -I{} --max-procs 2 bash -c '
   {
   echo sleep {}
   sleep 2s
   }'
17
nobar

Bashの組み込み機能でそれが可能になるとは思わない。

あなたがすることができます子供が終了したときに通知を取得します。

#!/bin/sh
set -o monitor        # enable script job control
trap 'echo "child died"' CHLD

しかし、シグナルハンドラで子の終了ステータスを取得する明らかな方法はありません。

その子ステータスを取得するのは、通常、下位レベルのPOSIX APIのwaitファミリーの関数の仕事です。残念ながらBashのサポートは制限されています - one 特定の子プロセスを待つ(そしてその終了ステータスを取得する)か all を待つことができ、常に0 。

不可能と思われるのは、waitpid(-1)と同じで、 any childプロセスが返されるまでブロックされます。

7
Alnitak

私はここにリストされているたくさんの良い例を見ます、私も投げ入れたいと思いました。

#! /bin/bash

items="1 2 3 4 5 6"
pids=""

for item in $items; do
    sleep $item &
    pids+="$! "
done

for pid in $pids; do
    wait $pid
    if [ $? -eq 0 ]; then
        echo "SUCCESS - Job $pid exited with a status of $?"
    else
        echo "FAILED - Job $pid exited with a status of $?"
    fi
done

私は非常によく似た方法でサーバー/サービスを起動/停止し、それぞれの終了ステータスをチェックします。私にとっては素晴らしい作品です。これが誰かに役立つことを願っています!

7
Jason Slobotski

次のコードはすべての計算が完了するのを待ち、 doCalculations のいずれかが失敗した場合は終了ステータス1を返します。

#!/bin/bash
for i in $(seq 0 9); do
   (doCalculations $i >&2 & wait %1; echo $?) &
done | grep -qv 0 && exit 1
5
errr

これは複数のpidに対して動作する私のバージョンで、実行に時間がかかりすぎると警告をログに記録し、実行に与えられた値より長い時間がかかるとサブプロセスを停止します。

function WaitForTaskCompletion {
    local pids="${1}" # pids to wait for, separated by semi-colon
    local soft_max_time="${2}" # If execution takes longer than $soft_max_time seconds, will log a warning, unless $soft_max_time equals 0.
    local hard_max_time="${3}" # If execution takes longer than $hard_max_time seconds, will stop execution, unless $hard_max_time equals 0.
    local caller_name="${4}" # Who called this function
    local exit_on_error="${5:-false}" # Should the function exit program on subprocess errors       

    Logger "${FUNCNAME[0]} called by [$caller_name]."

    local soft_alert=0 # Does a soft alert need to be triggered, if yes, send an alert once 
    local log_ttime=0 # local time instance for comparaison

    local seconds_begin=$SECONDS # Seconds since the beginning of the script
    local exec_time=0 # Seconds since the beginning of this function

    local retval=0 # return value of monitored pid process
    local errorcount=0 # Number of pids that finished with errors

    local pidCount # number of given pids

    IFS=';' read -a pidsArray <<< "$pids"
    pidCount=${#pidsArray[@]}

    while [ ${#pidsArray[@]} -gt 0 ]; do
        newPidsArray=()
        for pid in "${pidsArray[@]}"; do
            if kill -0 $pid > /dev/null 2>&1; then
                newPidsArray+=($pid)
            else
                wait $pid
                result=$?
                if [ $result -ne 0 ]; then
                    errorcount=$((errorcount+1))
                    Logger "${FUNCNAME[0]} called by [$caller_name] finished monitoring [$pid] with exitcode [$result]."
                fi
            fi
        done

        ## Log a standby message every hour
        exec_time=$(($SECONDS - $seconds_begin))
        if [ $((($exec_time + 1) % 3600)) -eq 0 ]; then
            if [ $log_ttime -ne $exec_time ]; then
                log_ttime=$exec_time
                Logger "Current tasks still running with pids [${pidsArray[@]}]."
            fi
        fi

        if [ $exec_time -gt $soft_max_time ]; then
            if [ $soft_alert -eq 0 ] && [ $soft_max_time -ne 0 ]; then
                Logger "Max soft execution time exceeded for task [$caller_name] with pids [${pidsArray[@]}]."
                soft_alert=1
                SendAlert

            fi
            if [ $exec_time -gt $hard_max_time ] && [ $hard_max_time -ne 0 ]; then
                Logger "Max hard execution time exceeded for task [$caller_name] with pids [${pidsArray[@]}]. Stopping task execution."
                kill -SIGTERM $pid
                if [ $? == 0 ]; then
                    Logger "Task stopped successfully"
                else
                    errrorcount=$((errorcount+1))
                fi
            fi
        fi

        pidsArray=("${newPidsArray[@]}")
        sleep 1
    done

    Logger "${FUNCNAME[0]} ended for [$caller_name] using [$pidCount] subprocesses with [$errorcount] errors."
    if [ $exit_on_error == true ] && [ $errorcount -gt 0 ]; then
        Logger "Stopping execution."
        exit 1337
    else
        return $errorcount
    fi
}

# Just a plain stupid logging function to replace with yours
function Logger {
    local value="${1}"

    echo $value
}

たとえば、3つのプロセスすべてが終了するのを待ち、実行時間が5秒より長い場合は警告を記録し、実行時間が120秒より長い場合はすべてのプロセスを停止します。失敗してもプログラムを終了しません。

function something {

    sleep 10 &
    pids="$!"
    sleep 12 &
    pids="$pids;$!"
    sleep 9 &
    pids="$pids;$!"

    WaitForTaskCompletion $pids 5 120 ${FUNCNAME[0]} false
}
# Launch the function
someting
5
Orsiris de Jong

シェルから結果を保存するだけです。ファイル内。

#!/bin/bash
tmp=/tmp/results

: > $tmp  #clean the file

for i in `seq 0 9`; do
  (doCalculations $i; echo $i:$?>>$tmp)&
done      #iterate

wait      #wait until all ready

sort $tmp | grep -v ':0'  #... handle as required
5
estani

Bash 4.2以降が利用可能な場合は、以下が役に立つかもしれません。これは、タスク名とその「コード」、さらにタスク名とそのPIDを格納するために連想配列を使用します。私はまたあなたのタスクが多くのCPUまたはI/O時間を消費し、あなたが同時タスクの数を制限したい場合に便利になるかもしれない単純なレート制限方法を作りました。

スクリプトは最初のループですべてのタスクを起動し、2番目のタスクで結果を消費します。

これは単純な場合にはやややり過ぎですが、かなりきちんとしたことを可能にします。たとえば、各タスクのエラーメッセージを別の連想配列に格納し、すべてが解決した後にそれらを印刷することができます。

#! /bin/bash

main () {
    local -A pids=()
    local -A tasks=([task1]="echo 1"
                    [task2]="echo 2"
                    [task3]="echo 3"
                    [task4]="false"
                    [task5]="echo 5"
                    [task6]="false")
    local max_concurrent_tasks=2

    for key in "${!tasks[@]}"; do
        while [ $(jobs 2>&1 | grep -c Running) -ge "$max_concurrent_tasks" ]; do
            sleep 1 # gnu sleep allows floating point here...
        done
        ${tasks[$key]} &
        pids+=(["$key"]="$!")
    done

    errors=0
    for key in "${!tasks[@]}"; do
        pid=${pids[$key]}
        local cur_ret=0
        if [ -z "$pid" ]; then
            echo "No Job ID known for the $key process" # should never happen
            cur_ret=1
        else
            wait $pid
            cur_ret=$?
        fi
        if [ "$cur_ret" -ne 0 ]; then
            errors=$(($errors + 1))
            echo "$key (${tasks[$key]}) failed."
        fi
    done

    return $errors
}

main
4
stefanct

スクリプトを修正して、プロセスをバックグラウンドで並列化するようにしました。

私は(bashとkshの両方でSolaris上で)いくつかの実験をしましたが、 'wait'がゼロでない場合は終了ステータスを出力し、PID引数が与えられない場合はゼロ以外の終了を返すジョブのリストを出力します。例えば。

バッシュ:

$ sleep 20 && exit 1 &
$ sleep 10 && exit 2 &
$ wait
[1]-  Exit 2                  sleep 20 && exit 2
[2]+  Exit 1                  sleep 10 && exit 1

Ksh:

$ sleep 20 && exit 1 &
$ sleep 10 && exit 2 &
$ wait
[1]+  Done(2)                  sleep 20 && exit 2
[2]+  Done(1)                  sleep 10 && exit 1

この出力はstderrに書き込まれるので、OPの例に対する簡単な解決策は次のようになります。

#!/bin/bash

trap "rm -f /tmp/x.$$" EXIT

for i in `seq 0 9`; do
  doCalculations $i &
done

wait 2> /tmp/x.$$
if [ `wc -l /tmp/x.$$` -gt 0 ] ; then
  exit 1
fi

この間:

wait 2> >(wc -l)

また、tmpファイルなしでカウントを返します。これは、このようにしても使用できます。例えば、

wait 2> >(if [ `wc -l` -gt 0 ] ; then echo "ERROR"; fi)

しかし、これはtmpファイルのIMOほど便利ではありません。私はtmpファイルを避けるための便利な方法を見つけることができませんでしたが、サブシェルで "wait"を実行することも避けていましたが、これはまったくうまくいきませんでした。

4
Tosh
#!/bin/bash
set -m
for i in `seq 0 9`; do
  doCalculations $i &
done
while fg; do true; done
  • set -mはあなたがスクリプトでfgとbgを使うことを可能にします
  • fgは、最後のプロセスをフォアグラウンドに置くことに加えて、フォアグラウンドのプロセスと同じ終了ステータスを持ちます。
  • fgがゼロ以外の終了ステータスで終了すると、while fgはループを停止します

残念ながら、これはバックグラウンドのプロセスが0以外の終了ステータスで終了した場合には処理できません。 (ループはすぐには終了しません。前のプロセスが完了するのを待ちます。)

3
Jayen

これはうまくいきます、@ HoverHellの答えより良くないにしても同じくらい良いはずです!

#!/usr/bin/env bash

set -m # allow for job control
EXIT_CODE=0;  # exit code of overall script

function foo() {
     echo "CHLD exit code is $1"
     echo "CHLD pid is $2"
     echo $(jobs -l)

     for job in `jobs -p`; do
         echo "PID => ${job}"
         wait ${job} ||  echo "At least one test failed with exit code => $?" ; EXIT_CODE=1
     done
}

trap 'foo $? $$' CHLD

DIRN=$(dirname "$0");

commands=(
    "{ echo "foo" && exit 4; }"
    "{ echo "bar" && exit 3; }"
    "{ echo "baz" && exit 5; }"
)

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 for all to finish
wait;

echo "EXIT_CODE => $EXIT_CODE"
exit "$EXIT_CODE"

# end

もちろん、このスクリプトをNPMプロジェクトで不死化しました。これにより、bashコマンドを並行して実行でき、テストに役立ちます。

https://github.com/ORESoftware/generic-subshel​​l

3
Alexander Mills

私はこれを試してみて、ここに他の例からすべての最高の部分を組み合わせました。このスクリプトは any backgroundプロセスが終了するとcheckpids関数を実行し、ポーリングに頼らずに終了ステータスを出力します。

#!/bin/bash

set -o monitor

sleep 2 &
sleep 4 && exit 1 &
sleep 6 &

pids=`jobs -p`

checkpids() {
    for pid in $pids; do
        if kill -0 $pid 2>/dev/null; then
            echo $pid is still alive.
        Elif wait $pid; then
            echo $pid exited with zero exit status.
        else
            echo $pid exited with non-zero exit status.
        fi
    done
    echo
}

trap checkpids CHLD

wait
3
michaelt

ここにはすでにたくさんの答えがありますが、私は誰も配列の使用を提案していないように思われることに驚いています。

n=10 # run 10 jobs
c=0
PIDS=()

while true

    my_function_or_command &
    PID=$!
    echo "Launched job as PID=$PID"
    PIDS+=($PID)

    (( c+=1 ))

    # required to prevent any exit due to error
    # caused by additional commands run which you
    # may add when modifying this example
    true

do

    if (( c < n ))
    then
        continue
    else
        break
    fi
done 


# collect launched jobs

for pid in "${PIDS[@]}"
do
    wait $pid || echo "failed job PID=$pid"
done
2
user3728501
set -e
fail () {
    touch .failure
}
expect () {
    wait
    if [ -f .failure ]; then
        rm -f .failure
        exit 1
    fi
}

sleep 2 || fail &
sleep 2 && false || fail &
sleep 2 || fail
expect

先頭のset -eは失敗したときにあなたのスクリプトを停止させます。

サブジョブが失敗した場合、expect1を返します。

2
Yajo

トラップはあなたの友達です。多くのシステムでERRをトラップすることができます。すべてのコマンドの後にコードの一部を実行するために、EXITまたはDEBUGでトラップすることができます。

これはすべての標準シグナルに加えて。

2
Paul Hodges

同時に到着した場合、いくつかのシグナルを失う可能性があるため、CHLDシグナルのトラッピングは機能しない可能性があります。

#!/bin/bash

trap 'rm -f $tmpfile' EXIT

tmpfile=$(mktemp)

doCalculations() {
    echo start job $i...
    sleep $((RANDOM % 5)) 
    echo ...end job $i
    exit $((RANDOM % 10))
}

number_of_jobs=10

for i in $( seq 1 $number_of_jobs )
do
    ( trap "echo job$i : exit value : \$? >> $tmpfile" EXIT; doCalculations ) &
done

wait 

i=0
while read res; do
    echo "$res"
    let i++
done < "$tmpfile"

echo $i jobs done !!!
1
mug896

いくつかのサブプロセスを待機し、それらのいずれかがゼロ以外のステータスコードで終了したときに終了する解決策は、 'wait -n'を使用することです。

#!/bin/bash
wait_for_pids()
{
    for (( i = 1; i <= $#; i++ )) do
        wait -n $@
        status=$?
        echo "received status: "$status
        if [ $status -ne 0 ] && [ $status -ne 127 ]; then
            exit 1
        fi
    done
}

sleep_for_10()
{
    sleep 10
    exit 10
}

sleep_for_20()
{
    sleep 20
}

sleep_for_10 &
pid1=$!

sleep_for_20 &
pid2=$!

wait_for_pids $pid2 $pid1

ステータスコード '127'は、存在しないプロセスのためのものです。これは、子が終了した可能性があることを意味します。

1
user3898515

私は最近これを使った(Alnitakのおかげで):

#!/bin/bash
# activate child monitoring
set -o monitor

# locking subprocess
(while true; do sleep 0.001; done) &
pid=$!

# count, and kill when all done
c=0
function kill_on_count() {
    # you could kill on whatever criterion you wish for
    # I just counted to simulate bash's wait with no args
    [ $c -eq 9 ] && kill $pid
    c=$((c+1))
    echo -n '.' # async feedback (but you don't know which one)
}
trap "kill_on_count" CHLD

function save_status() {
    local i=$1;
    local rc=$2;
    # do whatever, and here you know which one stopped
    # but remember, you're called from a subshell
    # so vars have their values at fork time
}

# care must be taken not to spawn more than one child per loop
# e.g don't use `seq 0 9` here!
for i in {0..9}; do
    (doCalculations $i; save_status $i $?) &
done

# wait for locking subprocess to be killed
wait $pid
echo

そこから簡単に推定することができ、トリガー(ファイルに触れる、シグナルを送る)を持ち、そのトリガーに応答するためにカウント基準(タッチしたファイルなど)を変更することができます。あるいは、 'any'以外のゼロのrcが欲しいのなら、save_statusからロックを解除してください。

1
Lloeki

これが必要でしたが、ターゲットプロセスは現在のシェルの子ではありませんでした。その場合、wait $PIDは機能しません。代わりに次のような方法を見つけました。

while [ -e /proc/$PID ]; do sleep 0.1 ; done

これは procfs の存在に依存していますが、これは利用できないかもしれません(例えばMacでは提供されていません)。そのため、移植性のために、代わりにこれを使用することができます。

while ps -p $PID >/dev/null ; do sleep 0.1 ; done
1
troelskn

これは私が使うものです:

#wait for jobs
for job in `jobs -p`; do wait ${job}; done
0
jplozier

ジョブを並行して実行し、ステータスを確認する最も直接的な方法は一時ファイルを使用することです。すでに似たような答えがいくつかあります(例:Nietzche-jouとmug896)。

#!/bin/bash
rm -f fail
for i in `seq 0 9`; do
  doCalculations $i || touch fail &
done
wait 
! [ -f fail ]

上記のコードはスレッドセーフではありません。上記のコードがそれ自体と同時に実行されるのではないかと心配な場合は、fail。$$のようにもっとユニークなファイル名を使うのが良いでしょう。最後の行は、「サブプロセスのいずれかがcode!= 0で終了したときに終了コード1を返す」という要件を満たすことです。私は片付けのためにそこに余分な要件を投げました。このように書くのがより明確になったかもしれません:

#!/bin/bash
trap 'rm -f fail.$$' EXIT
for i in `seq 0 9`; do
  doCalculations $i || touch fail.$$ &
done
wait 
! [ -f fail.$$ ] 

これは複数のジョブから結果を集めるための同様のスニペットです:私は一時ディレクトリを作成し、別々のファイルにすべてのサブタスクの出力をストーリー化し、そしてレビューのためにそれらをダンプします。これは本当に質問と一致しません - 私はボーナスとしてそれを投げ入れています:

#!/bin/bash
trap 'rm -fr $WORK' EXIT

WORK=/tmp/$$.work
mkdir -p $WORK
cd $WORK

for i in `seq 0 9`; do
  doCalculations $i >$i.result &
done
wait 
grep $ *  # display the results with filenames and contents
0
Mark

プロセスを待つ前にプロセスが完了している場合があります。既に終了しているプロセスを待つようにトリガすると、pidはこのシェルの子ではないなどのエラーが発生します。このような場合を回避するために、次の機能を使用してプロセスが完了したかどうかを調べることができます。

isProcessComplete(){
PID=$1
while [ -e /proc/$PID ]
do
    echo "Process: $PID is still running"
    sleep 5
done
echo "Process $PID has finished"
}
0
Anju Prasannan