![每秒精确运行一次循环](https://linux22.com/image/138231/%E6%AF%8F%E7%A7%92%E7%B2%BE%E7%A1%AE%E8%BF%90%E8%A1%8C%E4%B8%80%E6%AC%A1%E5%BE%AA%E7%8E%AF.png)
我正在运行这个循环来每秒检查并打印一些内容。然而,由于计算可能需要几百毫秒,因此打印时间有时会跳过一秒。
有没有办法编写这样一个循环,保证每秒都能得到打印输出? (当然,前提是循环中的计算时间少于一秒:))
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
这稍微改变了语义:如果你的东西花了不到一秒,它只会等待整秒过去。然而,如果你的东西由于某种原因花费了超过一秒的时间,它不会继续产生更多的子进程,永远不会结束。
所以你的东西永远不会并行运行,也不会在后台运行,因此变量也按预期工作。
请注意,如果您确实还启动了其他后台任务,则必须将指令更改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
- 它将显示秒数计数,但偶尔会跳过一秒。运行它watch -n 1 -p sleep 0.5
会每秒输出两次,每秒,你不会看到任何跳过。
答案3
在作为后台作业运行的子 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
从一秒中“窃取”的唯一时间是启动子 shell 所花费的时间,因此它最终会跳过一秒,但希望比原始代码少。
如果子 shell 中的代码最终使用更多的不到一秒钟,循环就会开始积累后台作业并最终耗尽资源。
答案4
和zsh
:
n=0
typeset -F SECONDS=0
while true; do
date '+%FT%T.%2N%z'
((++n > SECONDS)) && sleep $((n - SECONDS))
done
如果您的睡眠不支持浮点秒,您可以使用zsh
'szselect
代替(在 后面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
只要循环中的命令运行时间少于一秒,这些就不应该漂移。