正確に1秒ごとにループを実行します。

正確に1秒ごとにループを実行します。

毎秒何かを確認して印刷するためにこのループを実行しています。ただし、計算に数百ミリ秒かかることがあるため、印刷時間が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

答え1

元のコードに近づくために私がしたことは次のとおりです。

while true; do
  sleep 1 &
  ...your stuff here...
  wait # for sleep
done

これは意味を少し変更します。タスクに1秒未満がかかる場合は、1秒が経過するのを待ちます。ただし、何らかの理由でジョブが1秒以上かかる場合、より多くのサブプロセスは生成されず、絶対に終了しません。

したがって、ジョブは並列に実行されるか、バックグラウンドで実行されないため、変数は期待どおりに機能します。

他のバックグラウンドタスクも開始する場合は、そのプロセスのみを具体的waitに待つように指示を変更する必要があります。sleep

より正確にする必要がある場合は、システムクロックに同期して、フル秒ではなくミリ秒間スリープモードに切り替えることができます。


システムクロックと同期するには?本当に分からない、愚かな試み:

基本:

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...(未変更のまま)

答え2

ループをスクリプト/onelinerに再構成できる場合は、最も簡単な方法は次を使用することです。watchそしてそのpreciseオプション。

次のコマンドを使用して効果を確認できますwatch -n 1 sleep 0.5。秒数を表示しますが、時々1秒をスキップすることもあります。これを実行すると、watch -n 1 -p sleep 0.5毎秒2回出力され、スキップする現象は表示されません。

答え3

バックグラウンドジョブとして実行されているサブシェルでジョブを実行すると、そのジョブが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秒未満では、ループはバックグラウンドタスクを蓄積し始め、最終的にリソースが不足します。

答え4

そしてzsh

n=0
typeset -F SECONDS=0
while true; do
  date '+%FT%T.%2N%z'
  ((++n > SECONDS)) && sleep $((n - SECONDS))
done

zshSleepが浮動小数点秒をサポートしていない場合は、代わりに 'を使用できます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秒未満実行される限り、ドリフトしないでください。

関連情報