每秒精确运行一次循环

for*_*rin 34 bash sleep timestamps

我正在运行此循环以每秒检查和打印一些内容。然而,因为计算可能需要几百毫秒,所以打印时间有时会跳过一秒。

有没有办法编写这样一个循环,保证我每秒都能得到打印输出?(当然,前提是循环中的计算需要不到一秒钟:))

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
Run Code Online (Sandbox Code Playgroud)

fro*_*utz 67

为了更接近原始代码,我所做的是:

while true; do
  sleep 1 &
  ...your stuff here...
  wait # for sleep
done
Run Code Online (Sandbox Code Playgroud)

这稍微改变了语义:如果你的东西用了不到一秒,它会等待整整一秒过去。但是,如果您的东西因任何原因需要超过一秒钟,它就不会继续产生更多的子进程,而且永远不会结束。

所以你的东西永远不会并行运行,而不是在后台运行,所以变量也按预期工作。

请注意,如果您还启动了其他后台任务,则必须将wait指令更改为仅sleep专门等待该进程。

如果你需要它更准确,你可能只需要将它同步到系统时钟和 sleep ms 而不是整秒。


如何同步到系统时钟?真的不知道,愚蠢的尝试:

默认:

while sleep 1
do
    date +%N
done
Run Code Online (Sandbox Code Playgroud)

输出:003511461 010510925 016081282 021643477 028504349 03...(持续增长)

同步:

 while sleep 0.$((1999999999 - 1$(date +%N)))
 do
     date +%N
 done
Run Code Online (Sandbox Code Playgroud)

输出:002648691 001098397 002514348 001293023 001679137 00...(保持不变)

  • 这个睡眠/等待技巧真的很聪明! (9认同)

小智 30

如果您可以将循环重组为脚本/oneliner,那么最简单的方法是使用watch及其precise选项。

你可以看到效果watch -n 1 sleep 0.5- 它会显示秒数,但偶尔会跳过一秒。运行它watch -n 1 -p sleep 0.5会每秒输出两次,每一秒,你不会看到任何跳过。


Kus*_*nda 11

在作为后台作业运行的子 shell 中运行操作将使它们不会对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
Run Code Online (Sandbox Code Playgroud)

从一秒中“偷走”的唯一时间是启动子shell 所花费的时间,因此它最终会跳过一秒,但希望比原始代码少。

如果子 shell 中的代码最终使用超过一秒的时间,循环将开始积累后台作业并最终耗尽资源。


der*_*ert 9

另一种选择(如果您不能使用,例如,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
Run Code Online (Sandbox Code Playgroud)

请注意,sleep 0.2模拟执行一些耗时约 200 毫秒的耗时任务。尽管如此,纳秒输出保持稳定(好吧,按照非实时操作系统标准)——它每秒发生一次:

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
Run Code Online (Sandbox Code Playgroud)

相差不到 1 毫秒,而且没有趋势。这很好;如果系统上有任何负载,您应该期望至少 10 毫秒的反弹——但随着时间的推移仍然没有漂移。也就是说,你不会失去一秒钟。


Sté*_*las 7

zsh

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

如果您的 sleep 不支持浮点秒,则可以使用zsh'szselect代替(在 a 之后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
Run Code Online (Sandbox Code Playgroud)

只要循环中的命令运行时间少于一秒,这些就不应该漂移。