このループを実行して、毎秒いくつかのことをチェックして出力します。ただし、計算には数百ミリ秒かかる場合があるため、出力された時間は1秒をスキップすることがあります。
毎秒プリントアウトすることが保証されているようなループを書く方法はありますか? (もちろん、ループ内の計算にかかる時間は1秒未満です:))
while true; do
TIME=$(date +%H:%M:%S)
# some calculations which take a few hundred milliseconds
FOO=...
BAR=...
printf '%s %s %s\n' $TIME $FOO $BAR
sleep 1
done
元のコードに少し近づけるために、私は次のことを行います。
while true; do
sleep 1 &
...your stuff here...
wait # for sleep
done
これはセマンティクスを少し変更します。もしも1秒もかからない場合は、1秒が経過するのを待つだけです。ただし、何かが何らかの理由で1秒以上かかる場合は、終了することなく、さらに多くのサブプロセスを生成し続けることはありません。
そのため、バックグラウンドではなく並列に実行されることはないため、変数も期待どおりに機能します。
追加のバックグラウンドタスクも開始する場合は、wait
命令を変更して、具体的にはsleep
プロセスのみを待機する必要があることに注意してください。
さらに正確にする必要がある場合は、システムクロックに同期して、秒単位ではなくmsをスリープさせるだけで済みます。
システムクロックに同期する方法まったくわからない、愚かな試み:
デフォルト:
while sleep 1
do
date +%N
done
出力:003511461 010510925 016081282 021643477 028504349 03 ...(増加し続ける)
同期済み:
while sleep 0.$((1999999999 - 1$(date +%N)))
do
date +%N
done
出力:002648691 001098397 002514348 001293023 001679137 00 ...(同じまま)
ループをスクリプト/ワンライナーに再構築できる場合、これを行う最も簡単な方法は watch
とそのprecise
オプションを使用することです。
watch -n 1 sleep 0.5
で効果を確認できます-秒のカウントアップが表示されますが、1秒をスキップする場合があります。 watch -n 1 -p sleep 0.5
として実行すると、毎秒2回、毎秒出力され、スキップは表示されません。
バックグラウンドジョブとして実行されるサブシェルで操作を実行すると、sleep
にそれほど干渉しなくなります。
while true; do
(
TIME=$(date +%T)
# some calculations which take a few hundred milliseconds
FOO=...
BAR=...
printf '%s %s %s\n' "$TIME" "$FOO" "$BAR"
) &
sleep 1
done
1秒から「盗まれた」唯一の時間は、サブシェルを起動するのにかかった時間になるため、最終的には1秒をスキップしますが、元のコードよりも頻度が低いことが望まれます。
サブシェルのコードが1秒以上moreを使用して終了すると、ループはバックグラウンドジョブを蓄積し始め、最終的にリソースが不足します。
別の代替方法(使用できない場合、たとえば、watch -p
Maelstromが示唆するように) sleepenh
[ manpage ]は、このために設計されています。
例:
#!/bin/sh
t=$(sleepenh 0)
while true; do
date +'sec=%s ns=%N'
sleep 0.2
t=$(sleepenh $t 1)
done
sleep 0.2
シミュレーションでは、200ms程度の時間を消費するタスクを実行します。それにもかかわらず、ナノ秒出力は安定しています(非リアルタイムOS標準によります)。毎秒1回発生します。
sec=1533663406 ns=840039402
sec=1533663407 ns=840105387
sec=1533663408 ns=840380678
sec=1533663409 ns=840175397
sec=1533663410 ns=840132883
sec=1533663411 ns=840263150
sec=1533663412 ns=840246082
sec=1533663413 ns=840259567
sec=1533663414 ns=840066687
それは1ms未満であり、傾向はありません。それはとても良いことです。システムに負荷がかかっている場合、少なくとも10ミリ秒のバウンスが予想されますが、時間の経過によるドリフトはありません。つまり、あなたは秒を失うことはありません。
zsh
の場合:
n=0
typeset -F SECONDS=0
while true; do
date '+%FT%T.%2N%z'
((++n > SECONDS)) && sleep $((n - SECONDS))
done
睡眠が浮動小数点秒をサポートしていない場合は、代わりにzsh
のzselect
を使用できます(zmodload zsh/zselect
の後)。
zmodload zsh/zselect
n=0
typeset -F SECONDS=0
while true; do
date '+%FZ%T.%2N%z'
((++n > SECONDS)) && zselect -t $((((n - SECONDS) * 100) | 0))
done
ループ内のコマンドの実行に1秒未満かかる限り、これらはドリフトしないはずです。
すべてのヘルパー(usleep、GNUsleep、sleepenhなど)が使用できないPOSIXシェルスクリプトとまったく同じ要件がありました。
参照してください: https://stackoverflow.com/a/54494216
#!/bin/sh
get_up()
{
read -r UP REST </proc/uptime
export UP=${UP%.*}${UP#*.}
}
wait_till_1sec_is_full()
{
while true; do
get_up
test $((UP-START)) -ge 100 && break
done
}
while true; do
get_up; START=$UP
your_code
wait_till_1sec_is_full
done