Reputation: 69
I'm asking for how to wait for the nextfull second? I dont mean sleep 1! For example we have the time 07:45:06.729. The last 729 means the milliseconds. So how we can i say wait until next full second? It would be 07:45:07.000
Thanks ;)
Upvotes: 4
Views: 2702
Reputation: 364338
This is like Matt's answer, but doesn't have a busy-wait to try to wake up early. This simplifies things a lot, because we never have to care about anything but the fractional-second part of the current time.
Use GNU date +%N
to get the nanoseconds portion of the current time, and sleep
for 1 - that.
sleep_to_next_second(){
#TODO: check for 0000 and don't sleep at all, instead of for 0.10000
sleep 0.$(printf '%04d' $((10000 - 10#$(date +%4N))))
}
(FIXME: to fix the special-case bug, you'd just do a text compare for 0000. If so, you're already at the start of a second; don't sleep. So capture the printf result into a local duration=$(...)
variable, or printf -v duration
to print into a variable.)
The tricks are:
numbers with leading zeros are treated as octal in a bash arithmetic context, so use 10#$date
to force base10.
Since bash can only do integer math, the obvious thing is to use fixed-point thousandths of a second or something, and tack on a leading decimal point. This makes leading zeros essential, so that you sleep for 0.092 seconds, not 0.92. I used printf '%04d'
to format my numbers.
experimental testing:
# echo instead of sleep to see what numbers we generate
while true;do echo 0.$(printf '%04d' $((10000 - 10#$(date +%4N))));done
...
0.0049
0.0035
0.0015
0.10000 ##### minor bug: sleeps for 0.1 sec if the time was bang-on a ten-thousandth
0.9987
0.9973
0.9960
while true;do date +"%s %N"; sleep 0.$(printf '%04d' $((10000 - 10#$(date +%4N))));done
1445301422 583340443
1445301423 003697512
1445301424 003506054
1445301425 003266620
1445301426 003776955
1445301427 003921242
1445301428 003018890
1445301429 002652820
# So typical accuracy is 0.003 secs after the rollover to the next second, on a near-idle desktop
Ubuntu 15.04: GNU bash/date/sleep on Linux 3.19 on Intel i5-2500k, idling at 1.6GHz, turbo 3.8GHz.
Upvotes: 6
Reputation: 159
A simple function without any magic
function sleepUntilTheNextSecond() {
local currentTimeMilliseconds
currentTimeMilliseconds=$(date +%s%3N)
local currentTimeSeconds=$(( currentTimeMilliseconds / 1000 ))
local targetTimeMilliseconds=$(( (currentTimeSeconds + 1) * 1000 ))
local sleepMilliseconds=$(( targetTimeMilliseconds - currentTimeMilliseconds ))
if (( sleepMilliseconds > 1000 )); then
echo "Invalid sleepMilliseconds (> 1000): $sleepMilliseconds"
exit 1
fi
sleep "0.$sleepMilliseconds"
}
Tested on GNU bash, version 4.1.2(2)-release (x86_64-redhat-linux-gnu)
Upvotes: 0
Reputation: 1393
The above answers are very clever BASH, but ultimately I kept running into strange edge case problems trying to do it only in BASH so I wrote a trivial C program instead that works perfectly every time and is totally simple and understandable.
// Wait until the next second rolls over
// Use sleep rather than polling
#include <unistd.h>
#include <sys/time.h>
int main(void) {
unsigned int microseconds;
struct timeval tv;
gettimeofday(&tv, NULL);
// Calculate time until next round second...
microseconds = 1000000 - (tv.tv_usec);
usleep(microseconds);
return(0);
}
https://github.com/bigjosh/De-Harak-Clock-Controller/blob/master/nextsecond.c
Build the utility with the commands...
gcc nextsecond.c
chmod +x nextsecond
You can test that it works by running...
./nextsecond; date "+%N"
It will print the number of nanoseconds since the last round second, which should always be less than 010000000, which is 10,000,000ns, which is 10ms.
Upvotes: 0
Reputation: 74720
This is more of a timer solution.
It requires GNU date
which can do nanoseconds with %N
and a sleep
that can do decimals, which GNU does and some others do.
Your mileage will vary on a heavily loaded system. This will skip seconds if it has to.
If accuracy on a loaded system is not that important then safety
can be lower (or just use Peter's simpler solution). The higher it is the more processor time you take, but it has a slightly higher chance of being on time on a heavily loaded system. Past a point, you're just wasting cpu time. Even then, if your system has better things to schedule, this will wait.
# ms to spend busy waiting * 10
safety=100
while true; do
if [ "$(date +%s)" != "$last_sec" ]; then
# Do whatever you need to here
date +"%S.%N"
# Then figure out how much to sleep until just
# before the next second.
sec_ns="$(date +"%s %4N")"
sec=${sec_ns:0:-5}
ms=${sec_ns:11}
last_sec=$sec
let sleep=10000-10#$ms-$safety
sleep 0.$(printf '%04d' $sleep)
fi
done
The calls to date
and sleep
are external binaries so there is some overhead to executing them. As mentioned in the comments, bash might not be the best tool for the job.
Also note this will fail after Sat Nov 20 17:46:39 UTC 2286 due to the use of substrings. Hopefully our computer overlords have removed bash from their os by then. Thanks for ruining my joke Peter =)
Running both
[vagrant@localhost ~]$ /tmp/wait_seconds.sh
32.376187825
33.002727030
34.001005707
35.001850686
36.002232134
37.001802235
^C
[vagrant@localhost ~]$ while sleep 0.$(printf '%04d' $((10000 - 10#$(date +%4N) - 1))); do date +"%S.%N"; done
41.006809812
42.005389515
43.005793726
44.004930099
45.004829293
46.005988337
Upvotes: 1
Reputation: 8412
Using bash
sleep echo "scale=3; $((1000 -$(echo $(cut -d '.' -f2 <<< "07:45:06.729"))))/1000"|bc
Let me break this down a little since it is confusing
$(cut -d '.' -f2 <<< "07:45:06.729")
# This will get the milliseconds portion
building on the above command line
echo "scale=3; $((1000 -$(echo $(cut -d '.' -f2 <<< "07:45:06.729"))))/1000"|bc
# This will gave you the fraction
After this pass the remainder to the sleep
Edited: Shorter bash codes used
Upvotes: 0
Reputation: 22761
The GNU sleep command accepts floating point parameters. So you could calculate the exact time to wait and call sleep with the result.
See https://superuser.com/questions/222301/unix-sleep-until-the-specified-time
and https://serverfault.com/questions/469247/how-do-i-sleep-for-a-millisecond-in-bash-or-ksh
Upvotes: 1