Sleep until a specific time/date

前端 未结 18 1968
栀梦
栀梦 2020-11-28 21:13

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\"-d

相关标签:
18条回答
  • 2020-11-28 21:40

    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

    0 讨论(0)
  • 2020-11-28 21:40

    You can calculate the number of seconds between now and the wake-up time and use the existing 'sleep' command.

    0 讨论(0)
  • 2020-11-28 21:41

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

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

    General method (Avoid useless forks!)

    (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.

    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!

    Recent kernel avoid using /proc/timer_list by user!!

    Under recent 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
    
    0 讨论(0)
  • 2020-11-28 21:41

    You can stop a process from executing, by sending it a SIGSTOP signal, and then get it to resume executing by sending it a SIGCONT signal.

    So you could stop your script by sending is a SIGSTOP:

    kill -SIGSTOP <pid>
    

    And then use the at deamon to wake it up by sending it a SIGCONT in the same way.

    Presumably, your script will inform at of when it wanted to be woken up before putting itself to sleep.

    0 讨论(0)
  • 2020-11-28 21:42

    I actually wrote https://tamentis.com/projects/sleepuntil/ for this exact purpose. It's a bit over-kill most of the code comes from BSD 'at' so it's fairly standard-compliant:

    $ sleepuntil noon && sendmail something
    
    0 讨论(0)
  • 2020-11-28 21:43

    To follow on SpoonMeiser's answer, here's a specific example:

    $cat ./reviveself
    
    #!/bin/bash
    
    # save my process ID
    rspid=$$
    
    # schedule my own resuscitation
    # /bin/sh seems to dislike the SIGCONT form, so I use CONT
    # at can accept specific dates and times as well as relative ones
    # you can even do something like "at thursday" which would occur on a 
    # multiple of 24 hours rather than the beginning of the day
    echo "kill -CONT $rspid"|at now + 2 minutes
    
    # knock myself unconscious
    # bash is happy with symbolic signals
    kill -SIGSTOP $rspid
    
    # do something to prove I'm alive
    date>>reviveself.out
    $
    
    0 讨论(0)
提交回复
热议问题