Running a loop precisely once per second

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP











up vote
32
down vote

favorite
6












I'm running this loop to check and print some things every second. However, because the calculations take maybe a few hundred milliseconds, the printed time sometimes skip a second.



Is there any way to write such a loop that I am guaranteed to get a printout every second? (Provided, of course, that the calculations in the loop take less than a second :))



while true; do
TIME=$(date +%H:%M:%S)
# some calculations which take a few hundred milliseconds
FOO=...
BAR=...
printf '%s %s %sn' $TIME $FOO $BAR
sleep 1
done






share|improve this question





















  • Possibly helpful: unix.stackexchange.com/q/60767/117549
    – Jeff Schaller
    Aug 6 at 15:18






  • 26




    Note that "precisely once per second" is not literally possible in most cases because you are (usually) running in userspace atop a preemptively multitasking kernel which will schedule your code as it sees fit (so that you might not regain control immediately after a sleep ends, for example). Unless you are writing C code which calls into the sched(7) API (POSIX: see <sched.h> and pages linked from there), you basically cannot have real-time guarantees of this form.
    – Kevin
    Aug 6 at 23:57











  • Just to back up what @Kevin said, using sleep() to try and get any sort of precise timing is doomed to failure, it only guarantees AT LEAST 1sec of sleep. If you really need precise timings you need to look at the system clock (see CLOCK_MONOTONIC) and trigger things based on time-since-last-event + 1s, and make sure you don't trip yourself up by taking >1sec to run, calculating the next time after some operation, etc.
    – John U
    Aug 7 at 12:07










  • just going to leave this here falsehoodsabouttime.com
    – alo Malbarez
    Aug 7 at 15:36










  • Precisely once a second = use a VCXO. A software-only solution will only get you to "good enough", but not precise.
    – Ian MacDonald
    Aug 8 at 14:42














up vote
32
down vote

favorite
6












I'm running this loop to check and print some things every second. However, because the calculations take maybe a few hundred milliseconds, the printed time sometimes skip a second.



Is there any way to write such a loop that I am guaranteed to get a printout every second? (Provided, of course, that the calculations in the loop take less than a second :))



while true; do
TIME=$(date +%H:%M:%S)
# some calculations which take a few hundred milliseconds
FOO=...
BAR=...
printf '%s %s %sn' $TIME $FOO $BAR
sleep 1
done






share|improve this question





















  • Possibly helpful: unix.stackexchange.com/q/60767/117549
    – Jeff Schaller
    Aug 6 at 15:18






  • 26




    Note that "precisely once per second" is not literally possible in most cases because you are (usually) running in userspace atop a preemptively multitasking kernel which will schedule your code as it sees fit (so that you might not regain control immediately after a sleep ends, for example). Unless you are writing C code which calls into the sched(7) API (POSIX: see <sched.h> and pages linked from there), you basically cannot have real-time guarantees of this form.
    – Kevin
    Aug 6 at 23:57











  • Just to back up what @Kevin said, using sleep() to try and get any sort of precise timing is doomed to failure, it only guarantees AT LEAST 1sec of sleep. If you really need precise timings you need to look at the system clock (see CLOCK_MONOTONIC) and trigger things based on time-since-last-event + 1s, and make sure you don't trip yourself up by taking >1sec to run, calculating the next time after some operation, etc.
    – John U
    Aug 7 at 12:07










  • just going to leave this here falsehoodsabouttime.com
    – alo Malbarez
    Aug 7 at 15:36










  • Precisely once a second = use a VCXO. A software-only solution will only get you to "good enough", but not precise.
    – Ian MacDonald
    Aug 8 at 14:42












up vote
32
down vote

favorite
6









up vote
32
down vote

favorite
6






6





I'm running this loop to check and print some things every second. However, because the calculations take maybe a few hundred milliseconds, the printed time sometimes skip a second.



Is there any way to write such a loop that I am guaranteed to get a printout every second? (Provided, of course, that the calculations in the loop take less than a second :))



while true; do
TIME=$(date +%H:%M:%S)
# some calculations which take a few hundred milliseconds
FOO=...
BAR=...
printf '%s %s %sn' $TIME $FOO $BAR
sleep 1
done






share|improve this question













I'm running this loop to check and print some things every second. However, because the calculations take maybe a few hundred milliseconds, the printed time sometimes skip a second.



Is there any way to write such a loop that I am guaranteed to get a printout every second? (Provided, of course, that the calculations in the loop take less than a second :))



while true; do
TIME=$(date +%H:%M:%S)
# some calculations which take a few hundred milliseconds
FOO=...
BAR=...
printf '%s %s %sn' $TIME $FOO $BAR
sleep 1
done








share|improve this question












share|improve this question




share|improve this question








edited Aug 7 at 11:04









Jeff Schaller

31.1k846105




31.1k846105









asked Aug 6 at 14:57









forthrin

800821




800821











  • Possibly helpful: unix.stackexchange.com/q/60767/117549
    – Jeff Schaller
    Aug 6 at 15:18






  • 26




    Note that "precisely once per second" is not literally possible in most cases because you are (usually) running in userspace atop a preemptively multitasking kernel which will schedule your code as it sees fit (so that you might not regain control immediately after a sleep ends, for example). Unless you are writing C code which calls into the sched(7) API (POSIX: see <sched.h> and pages linked from there), you basically cannot have real-time guarantees of this form.
    – Kevin
    Aug 6 at 23:57











  • Just to back up what @Kevin said, using sleep() to try and get any sort of precise timing is doomed to failure, it only guarantees AT LEAST 1sec of sleep. If you really need precise timings you need to look at the system clock (see CLOCK_MONOTONIC) and trigger things based on time-since-last-event + 1s, and make sure you don't trip yourself up by taking >1sec to run, calculating the next time after some operation, etc.
    – John U
    Aug 7 at 12:07










  • just going to leave this here falsehoodsabouttime.com
    – alo Malbarez
    Aug 7 at 15:36










  • Precisely once a second = use a VCXO. A software-only solution will only get you to "good enough", but not precise.
    – Ian MacDonald
    Aug 8 at 14:42
















  • Possibly helpful: unix.stackexchange.com/q/60767/117549
    – Jeff Schaller
    Aug 6 at 15:18






  • 26




    Note that "precisely once per second" is not literally possible in most cases because you are (usually) running in userspace atop a preemptively multitasking kernel which will schedule your code as it sees fit (so that you might not regain control immediately after a sleep ends, for example). Unless you are writing C code which calls into the sched(7) API (POSIX: see <sched.h> and pages linked from there), you basically cannot have real-time guarantees of this form.
    – Kevin
    Aug 6 at 23:57











  • Just to back up what @Kevin said, using sleep() to try and get any sort of precise timing is doomed to failure, it only guarantees AT LEAST 1sec of sleep. If you really need precise timings you need to look at the system clock (see CLOCK_MONOTONIC) and trigger things based on time-since-last-event + 1s, and make sure you don't trip yourself up by taking >1sec to run, calculating the next time after some operation, etc.
    – John U
    Aug 7 at 12:07










  • just going to leave this here falsehoodsabouttime.com
    – alo Malbarez
    Aug 7 at 15:36










  • Precisely once a second = use a VCXO. A software-only solution will only get you to "good enough", but not precise.
    – Ian MacDonald
    Aug 8 at 14:42















Possibly helpful: unix.stackexchange.com/q/60767/117549
– Jeff Schaller
Aug 6 at 15:18




Possibly helpful: unix.stackexchange.com/q/60767/117549
– Jeff Schaller
Aug 6 at 15:18




26




26




Note that "precisely once per second" is not literally possible in most cases because you are (usually) running in userspace atop a preemptively multitasking kernel which will schedule your code as it sees fit (so that you might not regain control immediately after a sleep ends, for example). Unless you are writing C code which calls into the sched(7) API (POSIX: see <sched.h> and pages linked from there), you basically cannot have real-time guarantees of this form.
– Kevin
Aug 6 at 23:57





Note that "precisely once per second" is not literally possible in most cases because you are (usually) running in userspace atop a preemptively multitasking kernel which will schedule your code as it sees fit (so that you might not regain control immediately after a sleep ends, for example). Unless you are writing C code which calls into the sched(7) API (POSIX: see <sched.h> and pages linked from there), you basically cannot have real-time guarantees of this form.
– Kevin
Aug 6 at 23:57













Just to back up what @Kevin said, using sleep() to try and get any sort of precise timing is doomed to failure, it only guarantees AT LEAST 1sec of sleep. If you really need precise timings you need to look at the system clock (see CLOCK_MONOTONIC) and trigger things based on time-since-last-event + 1s, and make sure you don't trip yourself up by taking >1sec to run, calculating the next time after some operation, etc.
– John U
Aug 7 at 12:07




Just to back up what @Kevin said, using sleep() to try and get any sort of precise timing is doomed to failure, it only guarantees AT LEAST 1sec of sleep. If you really need precise timings you need to look at the system clock (see CLOCK_MONOTONIC) and trigger things based on time-since-last-event + 1s, and make sure you don't trip yourself up by taking >1sec to run, calculating the next time after some operation, etc.
– John U
Aug 7 at 12:07












just going to leave this here falsehoodsabouttime.com
– alo Malbarez
Aug 7 at 15:36




just going to leave this here falsehoodsabouttime.com
– alo Malbarez
Aug 7 at 15:36












Precisely once a second = use a VCXO. A software-only solution will only get you to "good enough", but not precise.
– Ian MacDonald
Aug 8 at 14:42




Precisely once a second = use a VCXO. A software-only solution will only get you to "good enough", but not precise.
– Ian MacDonald
Aug 8 at 14:42










5 Answers
5






active

oldest

votes

















up vote
62
down vote



accepted










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)






share|improve this answer



















  • 8




    This sleep/wait trick is really clever !
    – philfr
    Aug 8 at 7:20










  • I'm wondering if all implementations of sleep handle fractional seconds?
    – jcaron
    Aug 9 at 8:15






  • 1




    @jcaron not all of them. but it works for gnu sleep and busybox sleep so it's not exotic. You could probably do a simple fallback like sleep 0.9 || sleep 1 as invalid parameter is pretty much the only reason for sleep to ever fail.
    – frostschutz
    Aug 9 at 8:34











  • @frostschutz I'd expect sleep 0.9 to be interpreted as sleep 0 by naïve implementations (given that's what atoi would do). Not sure if that would actually result in an error.
    – jcaron
    Aug 9 at 10:18






  • 1




    I'm happy to see this question sparked a lot of interest. Your suggestion and answer are very good. Not only does it keep within the second, it also sticks as close to the whole second as possible. Impressive! (PS! On a side note, one must install GNU Coreutils and use gdate on macOS to make date +%N work.)
    – forthrin
    Aug 9 at 12:16


















up vote
30
down vote













If you can restructure your loop into a script / oneliner then the simplest way to do this is with watch and its precise option.



You can see the effect with watch -n 1 sleep 0.5 - it will show seconds counting up, but will occasionally skip over a second. Running it as watch -n 1 -p sleep 0.5 will output twice per second, every second, and you won't see any skips.






share|improve this answer




























    up vote
    10
    down vote













    Running the operations in a subshell that runs as a background job would make them not interfere so much with the sleep.



    while true; do
    (
    TIME=$(date +%T)
    # some calculations which take a few hundred milliseconds
    FOO=...
    BAR=...
    printf '%s %s %sn' "$TIME" "$FOO" "$BAR"
    ) &
    sleep 1
    done


    The only time "stolen" from the one second would be the time taken to launch the subshell, so it would eventually skip a second, but hopefully less often than the original code.



    If the code in the subshell ends up using more than a second, the loop would start to accumulate background jobs and eventually run out of resources.






    share|improve this answer






























      up vote
      8
      down vote













      Another alternative (if you can't use, e.g., watch -p as Maelstrom suggests) is sleepenh [manpage], which is designed for this.



      Example:



      #!/bin/sh

      t=$(sleepenh 0)
      while true; do
      date +'sec=%s ns=%N'
      sleep 0.2
      t=$(sleepenh $t 1)
      done


      Note the sleep 0.2 in there the simulate doing some time-consuming task eating around 200ms. Despite that, the nanoseconds output remain stable (well, by non-realtime OS standards) — it happens once per second:



      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


      That's under 1ms different, and no trend. That's quite good; you should expect bounces of at least 10ms if there is any load on the system — but still no drift over time. I.e., you won't lose a second.






      share|improve this answer




























        up vote
        7
        down vote













        With zsh:



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


        If your sleep doesn't support floating point seconds, you can use zsh's zselect instead (after 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


        Those should not drift as long as the commands in the loop take less than one second to run.






        share|improve this answer























          Your Answer







          StackExchange.ready(function()
          var channelOptions =
          tags: "".split(" "),
          id: "106"
          ;
          initTagRenderer("".split(" "), "".split(" "), channelOptions);

          StackExchange.using("externalEditor", function()
          // Have to fire editor after snippets, if snippets enabled
          if (StackExchange.settings.snippets.snippetsEnabled)
          StackExchange.using("snippets", function()
          createEditor();
          );

          else
          createEditor();

          );

          function createEditor()
          StackExchange.prepareEditor(
          heartbeatType: 'answer',
          convertImagesToLinks: false,
          noModals: false,
          showLowRepImageUploadWarning: true,
          reputationToPostImages: null,
          bindNavPrevention: true,
          postfix: "",
          onDemand: true,
          discardSelector: ".discard-answer"
          ,immediatelyShowMarkdownHelp:true
          );



          );








           

          draft saved


          draft discarded


















          StackExchange.ready(
          function ()
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2funix.stackexchange.com%2fquestions%2f460836%2frunning-a-loop-precisely-once-per-second%23new-answer', 'question_page');

          );

          Post as a guest






























          5 Answers
          5






          active

          oldest

          votes








          5 Answers
          5






          active

          oldest

          votes









          active

          oldest

          votes






          active

          oldest

          votes








          up vote
          62
          down vote



          accepted










          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)






          share|improve this answer



















          • 8




            This sleep/wait trick is really clever !
            – philfr
            Aug 8 at 7:20










          • I'm wondering if all implementations of sleep handle fractional seconds?
            – jcaron
            Aug 9 at 8:15






          • 1




            @jcaron not all of them. but it works for gnu sleep and busybox sleep so it's not exotic. You could probably do a simple fallback like sleep 0.9 || sleep 1 as invalid parameter is pretty much the only reason for sleep to ever fail.
            – frostschutz
            Aug 9 at 8:34











          • @frostschutz I'd expect sleep 0.9 to be interpreted as sleep 0 by naïve implementations (given that's what atoi would do). Not sure if that would actually result in an error.
            – jcaron
            Aug 9 at 10:18






          • 1




            I'm happy to see this question sparked a lot of interest. Your suggestion and answer are very good. Not only does it keep within the second, it also sticks as close to the whole second as possible. Impressive! (PS! On a side note, one must install GNU Coreutils and use gdate on macOS to make date +%N work.)
            – forthrin
            Aug 9 at 12:16















          up vote
          62
          down vote



          accepted










          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)






          share|improve this answer



















          • 8




            This sleep/wait trick is really clever !
            – philfr
            Aug 8 at 7:20










          • I'm wondering if all implementations of sleep handle fractional seconds?
            – jcaron
            Aug 9 at 8:15






          • 1




            @jcaron not all of them. but it works for gnu sleep and busybox sleep so it's not exotic. You could probably do a simple fallback like sleep 0.9 || sleep 1 as invalid parameter is pretty much the only reason for sleep to ever fail.
            – frostschutz
            Aug 9 at 8:34











          • @frostschutz I'd expect sleep 0.9 to be interpreted as sleep 0 by naïve implementations (given that's what atoi would do). Not sure if that would actually result in an error.
            – jcaron
            Aug 9 at 10:18






          • 1




            I'm happy to see this question sparked a lot of interest. Your suggestion and answer are very good. Not only does it keep within the second, it also sticks as close to the whole second as possible. Impressive! (PS! On a side note, one must install GNU Coreutils and use gdate on macOS to make date +%N work.)
            – forthrin
            Aug 9 at 12:16













          up vote
          62
          down vote



          accepted







          up vote
          62
          down vote



          accepted






          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)






          share|improve this answer















          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)







          share|improve this answer















          share|improve this answer



          share|improve this answer








          edited Aug 6 at 15:34


























          answered Aug 6 at 15:20









          frostschutz

          24.1k14571




          24.1k14571







          • 8




            This sleep/wait trick is really clever !
            – philfr
            Aug 8 at 7:20










          • I'm wondering if all implementations of sleep handle fractional seconds?
            – jcaron
            Aug 9 at 8:15






          • 1




            @jcaron not all of them. but it works for gnu sleep and busybox sleep so it's not exotic. You could probably do a simple fallback like sleep 0.9 || sleep 1 as invalid parameter is pretty much the only reason for sleep to ever fail.
            – frostschutz
            Aug 9 at 8:34











          • @frostschutz I'd expect sleep 0.9 to be interpreted as sleep 0 by naïve implementations (given that's what atoi would do). Not sure if that would actually result in an error.
            – jcaron
            Aug 9 at 10:18






          • 1




            I'm happy to see this question sparked a lot of interest. Your suggestion and answer are very good. Not only does it keep within the second, it also sticks as close to the whole second as possible. Impressive! (PS! On a side note, one must install GNU Coreutils and use gdate on macOS to make date +%N work.)
            – forthrin
            Aug 9 at 12:16













          • 8




            This sleep/wait trick is really clever !
            – philfr
            Aug 8 at 7:20










          • I'm wondering if all implementations of sleep handle fractional seconds?
            – jcaron
            Aug 9 at 8:15






          • 1




            @jcaron not all of them. but it works for gnu sleep and busybox sleep so it's not exotic. You could probably do a simple fallback like sleep 0.9 || sleep 1 as invalid parameter is pretty much the only reason for sleep to ever fail.
            – frostschutz
            Aug 9 at 8:34











          • @frostschutz I'd expect sleep 0.9 to be interpreted as sleep 0 by naïve implementations (given that's what atoi would do). Not sure if that would actually result in an error.
            – jcaron
            Aug 9 at 10:18






          • 1




            I'm happy to see this question sparked a lot of interest. Your suggestion and answer are very good. Not only does it keep within the second, it also sticks as close to the whole second as possible. Impressive! (PS! On a side note, one must install GNU Coreutils and use gdate on macOS to make date +%N work.)
            – forthrin
            Aug 9 at 12:16








          8




          8




          This sleep/wait trick is really clever !
          – philfr
          Aug 8 at 7:20




          This sleep/wait trick is really clever !
          – philfr
          Aug 8 at 7:20












          I'm wondering if all implementations of sleep handle fractional seconds?
          – jcaron
          Aug 9 at 8:15




          I'm wondering if all implementations of sleep handle fractional seconds?
          – jcaron
          Aug 9 at 8:15




          1




          1




          @jcaron not all of them. but it works for gnu sleep and busybox sleep so it's not exotic. You could probably do a simple fallback like sleep 0.9 || sleep 1 as invalid parameter is pretty much the only reason for sleep to ever fail.
          – frostschutz
          Aug 9 at 8:34





          @jcaron not all of them. but it works for gnu sleep and busybox sleep so it's not exotic. You could probably do a simple fallback like sleep 0.9 || sleep 1 as invalid parameter is pretty much the only reason for sleep to ever fail.
          – frostschutz
          Aug 9 at 8:34













          @frostschutz I'd expect sleep 0.9 to be interpreted as sleep 0 by naïve implementations (given that's what atoi would do). Not sure if that would actually result in an error.
          – jcaron
          Aug 9 at 10:18




          @frostschutz I'd expect sleep 0.9 to be interpreted as sleep 0 by naïve implementations (given that's what atoi would do). Not sure if that would actually result in an error.
          – jcaron
          Aug 9 at 10:18




          1




          1




          I'm happy to see this question sparked a lot of interest. Your suggestion and answer are very good. Not only does it keep within the second, it also sticks as close to the whole second as possible. Impressive! (PS! On a side note, one must install GNU Coreutils and use gdate on macOS to make date +%N work.)
          – forthrin
          Aug 9 at 12:16





          I'm happy to see this question sparked a lot of interest. Your suggestion and answer are very good. Not only does it keep within the second, it also sticks as close to the whole second as possible. Impressive! (PS! On a side note, one must install GNU Coreutils and use gdate on macOS to make date +%N work.)
          – forthrin
          Aug 9 at 12:16













          up vote
          30
          down vote













          If you can restructure your loop into a script / oneliner then the simplest way to do this is with watch and its precise option.



          You can see the effect with watch -n 1 sleep 0.5 - it will show seconds counting up, but will occasionally skip over a second. Running it as watch -n 1 -p sleep 0.5 will output twice per second, every second, and you won't see any skips.






          share|improve this answer

























            up vote
            30
            down vote













            If you can restructure your loop into a script / oneliner then the simplest way to do this is with watch and its precise option.



            You can see the effect with watch -n 1 sleep 0.5 - it will show seconds counting up, but will occasionally skip over a second. Running it as watch -n 1 -p sleep 0.5 will output twice per second, every second, and you won't see any skips.






            share|improve this answer























              up vote
              30
              down vote










              up vote
              30
              down vote









              If you can restructure your loop into a script / oneliner then the simplest way to do this is with watch and its precise option.



              You can see the effect with watch -n 1 sleep 0.5 - it will show seconds counting up, but will occasionally skip over a second. Running it as watch -n 1 -p sleep 0.5 will output twice per second, every second, and you won't see any skips.






              share|improve this answer













              If you can restructure your loop into a script / oneliner then the simplest way to do this is with watch and its precise option.



              You can see the effect with watch -n 1 sleep 0.5 - it will show seconds counting up, but will occasionally skip over a second. Running it as watch -n 1 -p sleep 0.5 will output twice per second, every second, and you won't see any skips.







              share|improve this answer













              share|improve this answer



              share|improve this answer











              answered Aug 7 at 0:10









              Maelstrom

              40113




              40113




















                  up vote
                  10
                  down vote













                  Running the operations in a subshell that runs as a background job would make them not interfere so much with the sleep.



                  while true; do
                  (
                  TIME=$(date +%T)
                  # some calculations which take a few hundred milliseconds
                  FOO=...
                  BAR=...
                  printf '%s %s %sn' "$TIME" "$FOO" "$BAR"
                  ) &
                  sleep 1
                  done


                  The only time "stolen" from the one second would be the time taken to launch the subshell, so it would eventually skip a second, but hopefully less often than the original code.



                  If the code in the subshell ends up using more than a second, the loop would start to accumulate background jobs and eventually run out of resources.






                  share|improve this answer



























                    up vote
                    10
                    down vote













                    Running the operations in a subshell that runs as a background job would make them not interfere so much with the sleep.



                    while true; do
                    (
                    TIME=$(date +%T)
                    # some calculations which take a few hundred milliseconds
                    FOO=...
                    BAR=...
                    printf '%s %s %sn' "$TIME" "$FOO" "$BAR"
                    ) &
                    sleep 1
                    done


                    The only time "stolen" from the one second would be the time taken to launch the subshell, so it would eventually skip a second, but hopefully less often than the original code.



                    If the code in the subshell ends up using more than a second, the loop would start to accumulate background jobs and eventually run out of resources.






                    share|improve this answer

























                      up vote
                      10
                      down vote










                      up vote
                      10
                      down vote









                      Running the operations in a subshell that runs as a background job would make them not interfere so much with the sleep.



                      while true; do
                      (
                      TIME=$(date +%T)
                      # some calculations which take a few hundred milliseconds
                      FOO=...
                      BAR=...
                      printf '%s %s %sn' "$TIME" "$FOO" "$BAR"
                      ) &
                      sleep 1
                      done


                      The only time "stolen" from the one second would be the time taken to launch the subshell, so it would eventually skip a second, but hopefully less often than the original code.



                      If the code in the subshell ends up using more than a second, the loop would start to accumulate background jobs and eventually run out of resources.






                      share|improve this answer















                      Running the operations in a subshell that runs as a background job would make them not interfere so much with the sleep.



                      while true; do
                      (
                      TIME=$(date +%T)
                      # some calculations which take a few hundred milliseconds
                      FOO=...
                      BAR=...
                      printf '%s %s %sn' "$TIME" "$FOO" "$BAR"
                      ) &
                      sleep 1
                      done


                      The only time "stolen" from the one second would be the time taken to launch the subshell, so it would eventually skip a second, but hopefully less often than the original code.



                      If the code in the subshell ends up using more than a second, the loop would start to accumulate background jobs and eventually run out of resources.







                      share|improve this answer















                      share|improve this answer



                      share|improve this answer








                      edited Aug 6 at 15:11


























                      answered Aug 6 at 15:02









                      Kusalananda

                      102k13199315




                      102k13199315




















                          up vote
                          8
                          down vote













                          Another alternative (if you can't use, e.g., watch -p as Maelstrom suggests) is sleepenh [manpage], which is designed for this.



                          Example:



                          #!/bin/sh

                          t=$(sleepenh 0)
                          while true; do
                          date +'sec=%s ns=%N'
                          sleep 0.2
                          t=$(sleepenh $t 1)
                          done


                          Note the sleep 0.2 in there the simulate doing some time-consuming task eating around 200ms. Despite that, the nanoseconds output remain stable (well, by non-realtime OS standards) — it happens once per second:



                          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


                          That's under 1ms different, and no trend. That's quite good; you should expect bounces of at least 10ms if there is any load on the system — but still no drift over time. I.e., you won't lose a second.






                          share|improve this answer

























                            up vote
                            8
                            down vote













                            Another alternative (if you can't use, e.g., watch -p as Maelstrom suggests) is sleepenh [manpage], which is designed for this.



                            Example:



                            #!/bin/sh

                            t=$(sleepenh 0)
                            while true; do
                            date +'sec=%s ns=%N'
                            sleep 0.2
                            t=$(sleepenh $t 1)
                            done


                            Note the sleep 0.2 in there the simulate doing some time-consuming task eating around 200ms. Despite that, the nanoseconds output remain stable (well, by non-realtime OS standards) — it happens once per second:



                            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


                            That's under 1ms different, and no trend. That's quite good; you should expect bounces of at least 10ms if there is any load on the system — but still no drift over time. I.e., you won't lose a second.






                            share|improve this answer























                              up vote
                              8
                              down vote










                              up vote
                              8
                              down vote









                              Another alternative (if you can't use, e.g., watch -p as Maelstrom suggests) is sleepenh [manpage], which is designed for this.



                              Example:



                              #!/bin/sh

                              t=$(sleepenh 0)
                              while true; do
                              date +'sec=%s ns=%N'
                              sleep 0.2
                              t=$(sleepenh $t 1)
                              done


                              Note the sleep 0.2 in there the simulate doing some time-consuming task eating around 200ms. Despite that, the nanoseconds output remain stable (well, by non-realtime OS standards) — it happens once per second:



                              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


                              That's under 1ms different, and no trend. That's quite good; you should expect bounces of at least 10ms if there is any load on the system — but still no drift over time. I.e., you won't lose a second.






                              share|improve this answer













                              Another alternative (if you can't use, e.g., watch -p as Maelstrom suggests) is sleepenh [manpage], which is designed for this.



                              Example:



                              #!/bin/sh

                              t=$(sleepenh 0)
                              while true; do
                              date +'sec=%s ns=%N'
                              sleep 0.2
                              t=$(sleepenh $t 1)
                              done


                              Note the sleep 0.2 in there the simulate doing some time-consuming task eating around 200ms. Despite that, the nanoseconds output remain stable (well, by non-realtime OS standards) — it happens once per second:



                              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


                              That's under 1ms different, and no trend. That's quite good; you should expect bounces of at least 10ms if there is any load on the system — but still no drift over time. I.e., you won't lose a second.







                              share|improve this answer













                              share|improve this answer



                              share|improve this answer











                              answered Aug 7 at 17:41









                              derobert

                              68.3k8147203




                              68.3k8147203




















                                  up vote
                                  7
                                  down vote













                                  With zsh:



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


                                  If your sleep doesn't support floating point seconds, you can use zsh's zselect instead (after 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


                                  Those should not drift as long as the commands in the loop take less than one second to run.






                                  share|improve this answer



























                                    up vote
                                    7
                                    down vote













                                    With zsh:



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


                                    If your sleep doesn't support floating point seconds, you can use zsh's zselect instead (after 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


                                    Those should not drift as long as the commands in the loop take less than one second to run.






                                    share|improve this answer

























                                      up vote
                                      7
                                      down vote










                                      up vote
                                      7
                                      down vote









                                      With zsh:



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


                                      If your sleep doesn't support floating point seconds, you can use zsh's zselect instead (after 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


                                      Those should not drift as long as the commands in the loop take less than one second to run.






                                      share|improve this answer















                                      With zsh:



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


                                      If your sleep doesn't support floating point seconds, you can use zsh's zselect instead (after 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


                                      Those should not drift as long as the commands in the loop take less than one second to run.







                                      share|improve this answer















                                      share|improve this answer



                                      share|improve this answer








                                      edited Aug 7 at 12:02


























                                      answered Aug 7 at 6:42









                                      Stéphane Chazelas

                                      279k53513845




                                      279k53513845






















                                           

                                          draft saved


                                          draft discarded


























                                           


                                          draft saved


                                          draft discarded














                                          StackExchange.ready(
                                          function ()
                                          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2funix.stackexchange.com%2fquestions%2f460836%2frunning-a-loop-precisely-once-per-second%23new-answer', 'question_page');

                                          );

                                          Post as a guest













































































                                          Comments

                                          Popular posts from this blog

                                          What is the equation of a 3D cone with generalised tilt?

                                          Relationship between determinant of matrix and determinant of adjoint?

                                          Color the edges and diagonals of a regular polygon