To stay a bit closer to the original code, what I do is:
while true; do
sleep 1 &
...your stuff here...
wait # for sleep
done
This changes the semantics a little: if your stuff took less than a second, it will simply wait for the full second to pass. However, if your stuff takes longer than a second for any reason, it won't keep spawning even more subprocesses with never any end to it.
So your stuff never runs in parallel, and not in the background, so variables work as expected too.
Note that if you do start additional background tasks as well, you'll have to change the wait
instruction to only wait for the sleep
process specifically.
If you need it to be even more accurate, you'll probably just have to sync it to the system clock and sleep ms instead of full seconds.
How to sync to system clock? No idea really, stupid attempt:
Default:
while sleep 1
do
date +%N
done
Output: 003511461 010510925 016081282 021643477 028504349 03... (keeps growing)
Synced:
while sleep 0.$((1999999999 - 1$(date +%N)))
do
date +%N
done
Output: 002648691 001098397 002514348 001293023 001679137 00... (stays same)
sched(7)
API (POSIX: see<sched.h>
and pages linked from there), you basically cannot have real-time guarantees of this form. – Kevin Aug 06 '18 at 23:57