Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Sleep until a specific time/date

Tags:

bash

sleep

wait

I want my bash script to sleep until a specific time. So, I want a command like "sleep" which takes no interval but an end time and sleeps until then.

The "at"-daemon is not a solution, as I need to block a running script until a certain date/time.

Is there such a command?

like image 296
theomega Avatar asked Mar 14 '09 14:03

theomega


2 Answers

As mentioned by Outlaw Programmer, I think the solution is just to sleep for the correct number of seconds.

To do this in bash, do the following:

current_epoch=$(date +%s) target_epoch=$(date -d '01/01/2010 12:00' +%s)  sleep_seconds=$(( $target_epoch - $current_epoch ))  sleep $sleep_seconds 

To add precision down to nanoseconds (effectively more around milliseconds) use e.g. this syntax:

current_epoch=$(date +%s.%N) target_epoch=$(date -d "20:25:00.12345" +%s.%N)  sleep_seconds=$(echo "$target_epoch - $current_epoch"|bc)  sleep $sleep_seconds 

Note that macOS / OS X does not support precision below seconds, you would need to use coreutils from brew instead → see these instructions

like image 123
SpoonMeiser Avatar answered Sep 21 '22 13:09

SpoonMeiser


Sample edited: Wed Apr 22 2020, something between 10:30 and 10h:55
(Important for reading samples)

General method (Avoid useless forks!)

As this question was asked 4 years ago, this first part concerns old bash versions:

(Nota: this method use date -f wich is no POSIX and don't work under MacOS! If under Mac, goto my pure bash function)

In order to reduce forks, instead of running date two times, I prefer to use this:

Simple starting sample

sleep $(( $(date -f - +%s- <<< "tomorrow 21:30"$'\nnow') 0 )) 

where tomorrow 21:30 could be replaced by any kind of date and format recognized by date, in the future .

if read -rp "Sleep until: "  targetTime ;then     sleep $(( $(date -f - +%s- <<< "$targetTime"$'\nnow') 0 )) fi 

With high precision (nanosec)

Nearly same:

sleep $(bc <<<s$(date -f - +'t=%s.%N;' <<<$'07:00 tomorrow\nnow')'st-t') 

Reaching next time

For reaching next HH:MM meaning today if possible, tomorrow if too late:

sleep $((($(date -f - +%s- <<<$'21:30 tomorrow\nnow')0)%86400)) 

This works under bash, ksh and other modern shells, but you have to use:

sleep $(( ( $(printf 'tomorrow 21:30\nnow\n' | date -f - +%s-)0 )%86400 )) 

under lighter shells like ash or dash.

Pure bash way, no fork!!

Tested under MacOS!

I wrote one two little functions: sleepUntil and sleepUntilHires

 Syntax:  sleepUntil [-q] <HH[:MM[:SS]]> [more days]      -q Quiet: don't print sleep computed argument      HH          Hours (minimal required argument)      MM          Minutes (00 if not set)      SS          Seconds (00 if not set)      more days   multiplied by 86400 (0 by default) 

As new versions of bash do offer a printf option to retrieve date, for this new way to sleep until HH:MM whithout using date or any other fork, I've build a little bash function. Here it is:

sleepUntil() { # args [-q] <HH[:MM[:SS]]> [more days]     local slp tzoff now quiet=false     [ "$1" = "-q" ] && shift && quiet=true     local -a hms=(${1//:/ })     printf -v now '%(%s)T' -1     printf -v tzoff '%(%z)T\n' $now     tzoff=$((0${tzoff:0:1}(3600*${tzoff:1:2}+60*${tzoff:3:2})))     slp=$((        ( 86400+(now-now%86400) + 10#$hms*3600 + 10#${hms[1]}*60 +           ${hms[2]}-tzoff-now ) %86400 + ${2:-0}*86400     ))     $quiet || printf 'sleep %ss, -> %(%c)T\n' $slp $((now+slp))     sleep $slp } 

Then:

sleepUntil 10:37 ; date +"Now, it is: %T" sleep 49s, -> Wed Apr 22 10:37:00 2020 Now, it is: 10:37:00  sleepUntil -q 10:37:44 ; date +"Now, it is: %T" Now, it is: 10:37:44  sleepUntil 10:50 1 ; date +"Now, it is: %T" sleep 86675s, -> Thu Apr 23 10:50:00 2020 ^C 

If target is before this will sleep until tomorrow:

sleepUntil 10:30 ; date +"Now, it is: %T" sleep 85417s, -> Thu Apr 23 10:30:00 2020 ^C  sleepUntil 10:30 1 ; date +"Now, it is: %T" sleep 171825s, -> Fri Apr 24 10:30:00 2020 ^C 

HiRes time with bash under GNU/Linux

Recent bash, from version 5.0 add new $EPOCHREALTIME variable with microseconds. From this there is a sleepUntilHires function.

sleepUntilHires () { # args [-q] <HH[:MM[:SS]]> [more days]     local slp tzoff now quiet=false musec musleep;     [ "$1" = "-q" ] && shift && quiet=true;     local -a hms=(${1//:/ });     printf -v now '%(%s)T' -1;     IFS=. read now musec <<< $EPOCHREALTIME;     musleep=$[2000000-10#$musec];     printf -v tzoff '%(%z)T\n' $now;     tzoff=$((0${tzoff:0:1}(3600*${tzoff:1:2}+60*${tzoff:3:2})));     slp=$(((( 86400 + ( now - now%86400 ) +             10#$hms*3600+10#${hms[1]}*60+10#${hms[2]} -             tzoff - now - 1             ) % 86400 ) + ${2:-0} * 86400           )).${musleep:1};     $quiet || printf 'sleep %ss, -> %(%c)T\n' $slp $((now+${slp%.*}+1));     read -t $slp foo } 

Please note: this use read -t wich is built-in, instead of sleep. Unfortunely, this won't work when running in background, without real TTY. Feel free to replace read -t by sleep if you plan to run this in background scripts... (But for background process, consider using cron and/or at instead of all this)

Skip next paragraph for tests and warning about $ËPOCHSECONDS!

Older method using /proc/timer_list, avoided to normal user, by recent Kernel!!

Under Linux kernel, you will find a variables file named `/proc/timer_list` where you could read an `offset` and a `now` variable, in **nanoseconds**. So we may compute sleep time to reach the *very top* desired time.

(I wrote this to generate and track specific events on very big log files, containing thousand line for one second).

mapfile  </proc/timer_list _timer_list for ((_i=0;_i<${#_timer_list[@]};_i++));do     [[ ${_timer_list[_i]} =~ ^now ]] && TIMER_LIST_SKIP=$_i     [[ ${_timer_list[_i]} =~ offset:.*[1-9] ]] && \     TIMER_LIST_OFFSET=${_timer_list[_i]//[a-z.: ]} && \      break done unset _i _timer_list readonly TIMER_LIST_OFFSET TIMER_LIST_SKIP  sleepUntilHires() {     local slp tzoff now quiet=false nsnow nsslp     [ "$1" = "-q" ] && shift && quiet=true     local hms=(${1//:/ })     mapfile -n 1 -s $TIMER_LIST_SKIP nsnow </proc/timer_list     printf -v now '%(%s)T' -1     printf -v tzoff '%(%z)T\n' $now     nsnow=$((${nsnow//[a-z ]}+TIMER_LIST_OFFSET))     nsslp=$((2000000000-10#${nsnow:${#nsnow}-9}))     tzoff=$((0${tzoff:0:1}(3600*${tzoff:1:2}+60*${tzoff:3:2})))     slp=$(( ( 86400 + ( now - now%86400 ) +             10#$hms*3600+10#${hms[1]}*60+${hms[2]} -             tzoff - now - 1         ) % 86400)).${nsslp:1}     $quiet || printf 'sleep %ss, -> %(%c)T\n' $slp $((now+${slp%.*}+1))     sleep $slp } 

After defining two read-only variables, TIMER_LIST_OFFSET and TIMER_LIST_SKIP, the function will access very quickly the variable file /proc/timer_list for computing sleep time:

Little test function

tstSleepUntilHires () {      local now next last     printf -v next "%(%H:%M:%S)T" $((${EPOCHREALTIME%.*}+1))     sleepUntilHires $next     date -f - +%F-%T.%N < <(echo now;sleep .92;echo now)     printf -v next "%(%H:%M:%S)T" $((${EPOCHREALTIME%.*}+1))     sleepUntilHires $next     date +%F-%T.%N } 

May render something like:

sleep 0.244040s, -> Wed Apr 22 10:34:39 2020 2020-04-22-10:34:39.001685312 2020-04-22-10:34:39.922291769 sleep 0.077012s, -> Wed Apr 22 10:34:40 2020 2020-04-22-10:34:40.004264869 
  • At begin of next second,
  • print time, then
  • wait 0.92 seccond, then
  • print time, then
  • compute 0.07 seconds left, to next second
  • sleep 0.07 seconds, then
  • print time.

Care to not mix $EPOCHSECOND and $EPOCHREALTIME!

Read my warning about difference between $EPOCHSECOND and $EPOCHREALTIME

This function use $EPOCHREALTIME so don't use $EPOCHSECOND for establishing next second:

Sample issue: Trying to print time next rounded by 2 seconds:

for i in 1 2;do     printf -v nextH "%(%T)T" $(((EPOCHSECONDS/2)*2+2))     sleepUntilHires $nextH     IFS=. read now musec <<<$EPOCHREALTIME     printf "%(%c)T.%s\n" $now $musec done 

May produce:

sleep 0.587936s, -> Wed Apr 22 10:51:26 2020 Wed Apr 22 10:51:26 2020.000630 sleep 86399.998797s, -> Thu Apr 23 10:51:26 2020 ^C 
like image 34
F. Hauri Avatar answered Sep 19 '22 13:09

F. Hauri