メインのBourne Shellスクリプトから呼び出されるコマンドCMDがありますが、これには永遠に時間がかかります。
スクリプトを次のように変更します。
誰かがこれを達成するためのポインタをくれますか?
1:bashでは、$!
は最後に実行されたバックグラウンドプロセスのPIDを保持します。とにかく、どのプロセスを監視するかがわかります。
4:wait <n>
は、PID <n>
のプロセスが完了するまで待機し(プロセスが完了するまでブロックするため、プロセスが完了したことを確認するまでこれを呼び出したくない場合があります)完了したプロセスの終了コード。
2、3:ps
またはps | grep " $! "
は、プロセスがまだ実行中かどうかを示します。出力を理解し、それが終了にどれだけ近いかを決定する方法はあなた次第です。 (ps | grep
はばかではありません。時間があれば、プロセスがまだ実行されているかどうかを確認するためのより堅牢な方法を考え出すことができます)。
スケルトンスクリプトは次のとおりです。
# simulate a long process that will have an identifiable exit code
(sleep 15 ; /bin/false) &
my_pid=$!
while ps | grep " $my_pid " # might also need | grep -v grep here
do
echo $my_pid is still in the ps output. Must still be running.
sleep 3
done
echo Oh, it looks like the process is done.
wait $my_pid
# The variable $? always holds the exit code of the last command to finish.
# Here it holds the exit code of $my_pid, since wait exits with that code.
my_status=$?
echo The exit status of the process was $my_status
これは私が同様のニーズを持っていたときにそれを解決した方法です:
# Some function that takes a long time to process
longprocess() {
# Sleep up to 14 seconds
sleep $((RANDOM % 15))
# Randomly exit with 0 or 1
exit $((RANDOM % 2))
}
pids=""
# Run five concurrent processes
for i in {1..5}; do
( longprocess ) &
# store PID of process
pids+=" $!"
done
# Wait for all processes to finnish, will take max 14s
for p in $pids; do
if wait $p; then
echo "Process $p success"
else
echo "Process $p fail"
fi
done
#/bin/bash
#pgm to monitor
tail -f /var/log/messages >> /tmp/log&
# background cmd pid
pid=$!
# loop to monitor running background cmd
while :
do
ps ax | grep $pid | grep -v grep
ret=$?
if test "$ret" != "0"
then
echo "Monitored pid ended"
break
fi
sleep 5
done
wait $pid
echo $?
私が見るように、ほとんどすべての回答は外部ユーティリティ(ほとんどps
)を使用してバックグラウンドプロセスの状態をポーリングします。さらにunixeshソリューションがあり、SIGCHLDシグナルをキャッチします。シグナルハンドラでは、どの子プロセスが停止したかを確認する必要があります。これは、kill -0 <PID>
組み込み(ユニバーサル)または/proc/<PID>
ディレクトリの存在の確認(Linux固有)またはjobs
組み込み(- bash specific。を使用して実行できます。jobs -l
もpidを報告します。この場合、出力の3番目のフィールドはStopped | Running | Done | Exitになります。
これが私の例です。
起動されたプロセスはloop.sh
と呼ばれます。 -x
または数値を引数として受け入れます。 -x
の場合、終了コード1で終了します。数値の場合、num * 5秒待機します。 5秒ごとに、PIDを出力します。
ランチャープロセスはlaunch.sh
と呼ばれます:
#!/bin/bash
handle_chld() {
local tmp=()
for((i=0;i<${#pids[@]};++i)); do
if [ ! -d /proc/${pids[i]} ]; then
wait ${pids[i]}
echo "Stopped ${pids[i]}; exit code: $?"
else tmp+=(${pids[i]})
fi
done
pids=(${tmp[@]})
}
set -o monitor
trap "handle_chld" CHLD
# Start background processes
./loop.sh 3 &
pids+=($!)
./loop.sh 2 &
pids+=($!)
./loop.sh -x &
pids+=($!)
# Wait until all background processes are stopped
while [ ${#pids[@]} -gt 0 ]; do echo "WAITING FOR: ${pids[@]}"; sleep 2; done
echo STOPPED
詳細については、以下を参照してください: bashスクリプトからのプロセスの開始に失敗しました
バックグラウンドの子プロセスのpidは$!に保存されます。すべての子プロセスのpidを配列に保存できます。 PIDS []。
wait [-n] [jobspec or pid …]
各プロセスID pidまたはジョブ仕様jobspecで指定された子プロセスが終了するまで待機し、最後に待機したコマンドの終了ステータスを返します。ジョブ仕様が指定されている場合、ジョブ内のすべてのプロセスが待機されます。引数が指定されていない場合、現在アクティブなすべての子プロセスが待機され、戻りステータスはゼロになります。 -nオプションが指定されている場合、waitはジョブが終了するまで待機し、終了ステータスを返します。 jobspecもpidもシェルのアクティブな子プロセスを指定しない場合、戻りステータスは127です。
waitコマンドを使用すると、すべての子プロセスの終了を待つことができます。一方、$?で各子プロセスの終了ステータスを取得し、ステータスをSTATUS []に保存できます。その後、ステータスに応じて何かを行うことができます。
次の2つのソリューションを試してみましたが、うまく動作します。 solution01はより簡潔ですが、solution02は少し複雑です。
#!/bin/bash
# start 3 child processes concurrently, and store each pid into array PIDS[].
process=(a.sh b.sh c.sh)
for app in ${process[@]}; do
./${app} &
PIDS+=($!)
done
# wait for all processes to finish, and store each process's exit code into array STATUS[].
for pid in ${PIDS[@]}; do
echo "pid=${pid}"
wait ${pid}
STATUS+=($?)
done
# after all processed finish, check their exit codes in STATUS[].
i=0
for st in ${STATUS[@]}; do
if [[ ${st} -ne 0 ]]; then
echo "$i failed"
else
echo "$i finish"
fi
((i+=1))
done
#!/bin/bash
# start 3 child processes concurrently, and store each pid into array PIDS[].
i=0
process=(a.sh b.sh c.sh)
for app in ${process[@]}; do
./${app} &
pid=$!
PIDS[$i]=${pid}
((i+=1))
done
# wait for all processes to finish, and store each process's exit code into array STATUS[].
i=0
for pid in ${PIDS[@]}; do
echo "pid=${pid}"
wait ${pid}
STATUS[$i]=$?
((i+=1))
done
# after all processed finish, check their exit codes in STATUS[].
i=0
for st in ${STATUS[@]}; do
if [[ ${st} -ne 0 ]]; then
echo "$i failed"
else
echo "$i finish"
fi
((i+=1))
done
あなたのアプローチを少し変えます。コマンドがまだ実行されているかどうかを数秒ごとに確認してメッセージを報告するのではなく、コマンドがまだ実行中であることを数秒ごとに報告する別のプロセスを用意し、コマンドが終了したらそのプロセスを強制終了します。例えば:
#!/ bin/sh cmd(){sleep 5; 24番出口; } cmd&#長時間実行プロセスを実行します pid = $! #pid #を記録します#echo "$(date):$ pid is still running";コマンドがまだ実行中であることを継続的に報告するプロセスを生成します 寝る1; done& echoer = $! #プロセスの終了時にレポーターを強制終了するトラップを設定します trap 'kill $ echoer' 0 #プロセスが完了するまで待機します wait $ pid; then echo "cmd successed" else echo "cmd FAILED !!(returned $?)" fi
私たちのチームは、25分間何も操作しないとタイムアウトするリモートSSH実行スクリプトについても同じニーズを抱えていました。監視ループがバックグラウンドプロセスを1秒ごとにチェックし、非アクティブタイムアウトを抑制するために10分ごとに印刷するソリューションを次に示します。
long_running.sh &
pid=$!
# Wait on a background job completion. Query status every 10 minutes.
declare -i elapsed=0
# `ps -p ${pid}` works on macOS and CentOS. On both OSes `ps ${pid}` works as well.
while ps -p ${pid} >/dev/null; do
sleep 1
if ((++elapsed % 600 == 0)); then
echo "Waiting for the completion of the main script. $((elapsed / 60))m and counting ..."
fi
done
# Return the exit code of the terminated background process. This works in Bash 4.4 despite what Bash docs say:
# "If neither jobspec nor pid specifies an active child process of the Shell, the return status is 127."
wait ${pid}
上記のソリューションに似た簡単な例。これには、プロセス出力を監視する必要はありません。次の例では、tailを使用して出力を追跡しています。
$ echo '#!/bin/bash' > tmp.sh
$ echo 'sleep 30; exit 5' >> tmp.sh
$ chmod +x tmp.sh
$ ./tmp.sh &
[1] 7454
$ pid=$!
$ wait $pid
[1]+ Exit 5 ./tmp.sh
$ echo $?
5
Tailを使用してプロセス出力を追跡し、プロセスが完了したら終了します。
$ echo '#!/bin/bash' > tmp.sh
$ echo 'i=0; while let "$i < 10"; do sleep 5; echo "$i"; let i=$i+1; done; exit 5;' >> tmp.sh
$ chmod +x tmp.sh
$ ./tmp.sh
0
1
2
^C
$ ./tmp.sh > /tmp/tmp.log 2>&1 &
[1] 7673
$ pid=$!
$ tail -f --pid $pid /tmp/tmp.log
0
1
2
3
4
5
6
7
8
9
[1]+ Exit 5 ./tmp.sh > /tmp/tmp.log 2>&1
$ wait $pid
$ echo $?
5
別の解決策は、procファイルシステムを介してプロセスを監視することです(ps/grepコンボより安全です)。プロセスを開始すると、対応するフォルダーが/ proc/$ pidにあるため、ソリューションは
#!/bin/bash
....
doSomething &
local pid=$!
while [ -d /proc/$pid ]; do # While directory exists, the process is running
doSomethingElse
....
else # when directory is removed from /proc, process has ended
wait $pid
local exit_status=$?
done
....
これで、好きなように$ exit_status変数を使用できます。
この方法を使用すると、スクリプトはバックグラウンドプロセスを待つ必要がなくなり、一時ファイルの終了ステータスを監視するだけで済みます。
FUNCmyCmd() { sleep 3;return 6; };
export retFile=$(mktemp);
FUNCexecAndWait() { FUNCmyCmd;echo $? >$retFile; };
FUNCexecAndWait&
現在、retFileの内容を監視し続けるだけで、スクリプトは他に何でもできます(終了時間など、必要な他の情報を含めることもできます)。
PS .:ところで、私はbashで思考をコーディングしました
これはあなたの質問を超えて広がっているかもしれませんが、プロセスが実行されている時間の長さを心配している場合は、一定時間後に実行中のバックグラウンドプロセスのステータスを確認することができます。 pgrep -P $$
を使用して、どの子PIDがまだ実行されているかを確認するのは簡単ですが、既に期限が切れているPIDの終了ステータスを確認するために次の解決策を思い付きました。
cmd1() { sleep 5; exit 24; }
cmd2() { sleep 10; exit 0; }
pids=()
cmd1 & pids+=("$!")
cmd2 & pids+=("$!")
lasttimeout=0
for timeout in 2 7 11; do
echo -n "interval-$timeout: "
sleep $((timeout-lasttimeout))
# you can only wait on a pid once
remainingpids=()
for pid in ${pids[*]}; do
if ! ps -p $pid >/dev/null ; then
wait $pid
echo -n "pid-$pid:exited($?); "
else
echo -n "pid-$pid:running; "
remainingpids+=("$pid")
fi
done
pids=( ${remainingpids[*]} )
lasttimeout=$timeout
echo
done
どの出力:
interval-2: pid-28083:running; pid-28084:running;
interval-7: pid-28083:exited(24); pid-28084:running;
interval-11: pid-28084:exited(0);
注:$pids
を配列ではなく文字列変数に変更して、必要に応じて簡単にすることができます。