Search898
Search898

Reputation: 69

Bash command wait until next full second

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

Answers (6)

Peter Cordes
Peter Cordes

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

Garlaro
Garlaro

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

bigjosh
bigjosh

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

Matt
Matt

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

repzero
repzero

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

sleeplessnerd
sleeplessnerd

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

Related Questions