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?
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
Sample edited: Wed Apr 22 2020, something between 10:30 and 10h:55
(Important for reading samples)
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:
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
Nearly same:
sleep $(bc <<<s$(date -f - +'t=%s.%N;' <<<$'07:00 tomorrow\nnow')'st-t')
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.
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
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
!
/proc/timer_list
, avoided to normal user, by recent Kernel!!(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:
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
$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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With