代码之家  ›  专栏  ›  技术社区  ›  theomega

Bash:睡到特定的时间/日期

  •  70
  • theomega  · 技术社区  · 16 年前

    “at”-守护进程不是解决方案,因为我需要在某个日期/时间之前阻止正在运行的脚本。

    有这样的命令吗?

    16 回复  |  直到 5 年前
        1
  •  111
  •   Community CDub    8 年前

    正如非法程序员所提到的,我认为解决方法就是睡眠正确的秒数。

    要在bash中执行此操作,请执行以下操作:

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

    要将精度降低到纳秒(更有效的是毫秒左右),请使用以下语法:

    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
    

    请注意,macOS/OS X不支持低于秒的精度,您需要使用 coreutils brew 相反→ see these instructions

        2
  •  33
  •   F. Hauri - Give Up GitHub    3 年前

    样本编辑:2020年4月22日星期三,10:30到10:55之间

    正如4年前提出这个问题一样,第一部分涉及 古老的 bash版本:

    (注:此方法使用 date -f 作用 )

    为了减少 forks ,而不是跑步 date 有两次,我更喜欢用这个:

    简单起始样本

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

    哪里 tomorrow 21:30 可以由任何类型的日期和格式替换 日期 未来

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

    几乎相同:

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

    到达

    为了达到 HH:MM 如果可能,指今天;如果太晚,指明天:

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

    ,

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

    打火机 贝壳状 .

    纯净的 路,没有叉子!!

    我写 sleepUntil 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)
    

    新版本的bash确实提供了 printf 用于检索日期的选项,用于此新方法 睡到HH:MM 不使用 日期

    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
    }
    

    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
    

    如果目标是 之前

    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
    

    在GNU/Linux下

    ,从版本5.0添加新的 $EPOCHREALTIME 以微秒为单位的变量。从这里有一个 夜猫子

    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
    }
    

    请注意:此项使用 read -t wich是内置的,而不是 sleep . 不幸的是,如果没有真正的TTY,在后台运行时这将不起作用。请随意更换 read-t 通过 睡觉 计划在后台脚本中运行此操作。。。(但对于后台进程,考虑使用) cron 和/或 at

    跳过下一段,了解有关的测试和警告 $ËPOCHSECONDS !

    /proc/timer_list ,通过最近的内核避免了普通用户!!

    (我写这篇文章是为了在非常大的日志文件上生成和跟踪特定事件,一秒钟包含1000行)。

    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
    }
    

    在定义了两个 变量, TIMER_LIST_OFFSET TIMER_LIST_SKIP ,函数将非常快速地访问变量文件 /进程/计时器列表 计算睡眠时间:

    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
    }
    

    可能会出现以下情况:

    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
    
    • 在下一秒开始时,
    • 那么打印时间呢
    • 那么打印时间呢
    • 计算剩余0.07秒到下一秒
    • 打印时间。

    注意不要混在一起 $EPOCHSECOND $EPOCHREALTIME

    读我的 warning about difference between $EPOCHSECOND and $EPOCHREALTIME

    $EPOCHREALTIME 所以不要使用 建立 下一秒

    示例问题:下一次尝试打印时间四舍五入为2秒:

    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
    

    可能产生:

    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
    
        3
  •  23
  •   Steve Schnepp    13 年前

    使用 sleep date . 你会想用 date -d

    expr `date -d "next week" +%s` - `date -d "now" +%s`
    

    只需将“下周”替换为您希望等待的任何日期,然后将此表达式指定给一个值,然后睡眠几秒钟:

    startTime=$(date +%s)
    endTime=$(date -d "next week" +%s)
    timeToWait=$(($endTime- $startTime))
    sleep $timeToWait
    

    全部完成!

        4
  •  10
  •   Camusensei    7 年前

    cron 在windows上工作的步骤)

    • 检测系统时间变化和自适应
    • 智能输出,指示剩余时间
    • 24小时输入格式
    • &&

    样本运行

    $ til 13:00 && date
    1 hour and 18 minutes and 26 seconds left...
    1 hour and 18 minutes left...
    1 hour and 17 minutes left...
    1 hour and 16 minutes left...
    1 hour and 15 minutes left...
    1 hour and 14 minutes left...
    1 hour and 10 minutes left...
    1 hour and  5 minutes left...
    1 hour and  0 minutes left...
    55 minutes left...
    50 minutes left...
    45 minutes left...
    40 minutes left...
    35 minutes left...
    30 minutes left...
    25 minutes left...
    20 minutes left...
    15 minutes left...
    10 minutes left...
     5 minutes left...
     4 minutes left...
     3 minutes left...
     2 minutes left...
     1 minute left...
    Mon, May 18, 2015  1:00:00 PM
    

    (末尾的日期不是函数的一部分,而是由于 && date

    til(){
      local hour mins target now left initial sleft correction m sec h hm hs ms ss showSeconds toSleep
      showSeconds=true
      [[ $1 =~ ([0-9][0-9]):([0-9][0-9]) ]] || { echo >&2 "USAGE: til HH:MM"; return 1; }
      hour=${BASH_REMATCH[1]} mins=${BASH_REMATCH[2]}
      target=$(date +%s -d "$hour:$mins") || return 1
      now=$(date +%s)
      (( target > now )) || target=$(date +%s -d "tomorrow $hour:$mins")
      left=$((target - now))
      initial=$left
      while (( left > 0 )); do
        if (( initial - left < 300 )) || (( left < 300 )) || [[ ${left: -2} == 00 ]]; then
          # We enter this condition:
          # - once every 5 minutes
          # - every minute for 5 minutes after the start
          # - every minute for 5 minutes before the end
          # Here, we will print how much time is left, and re-synchronize the clock
    
          hs= ms= ss=
          m=$((left/60)) sec=$((left%60)) # minutes and seconds left
          h=$((m/60)) hm=$((m%60)) # hours and minutes left
    
          # Re-synchronise
          now=$(date +%s) sleft=$((target - now)) # recalculate time left, multiple 60s sleeps and date calls have some overhead.
          correction=$((sleft-left))
          if (( ${correction#-} > 59 )); then
            echo "System time change detected..."
            (( sleft <= 0 )) && return # terminating as the desired time passed already
            til "$1" && return # resuming the timer anew with the new time
          fi
    
          # plural calculations
          (( sec > 1 )) && ss=s
          (( hm != 1 )) && ms=s
          (( h > 1 )) && hs=s
    
          (( h > 0 )) && printf %s "$h hour$hs and "
          (( h > 0 || hm > 0 )) && printf '%2d %s' "$hm" "minute$ms"
          if [[ $showSeconds ]]; then
            showSeconds=
            (( h > 0 || hm > 0 )) && (( sec > 0 )) && printf %s " and "
            (( sec > 0 )) && printf %s "$sec second$ss"
            echo " left..."
            (( sec > 0 )) && sleep "$sec" && left=$((left-sec)) && continue
          else
            echo " left..."
          fi
        fi
        left=$((left-60))
        sleep "$((60+correction))"
        correction=0
      done
    }
    
        5
  •  10
  •   Anthony O.    3 年前

    sleep $(expr $(date -d "03/21/2014 12:30" +%s) - $(date +%s))
    
        6
  •  8
  •   SpoonMeiser    16 年前

    您可以通过发送SIGSTOP信号来停止进程的执行,然后通过发送SIGCONT信号使其恢复执行。

    kill -SIGSTOP <pid>
    

    然后使用at deamon以同样的方式发送一个SIGCONT来唤醒它。

        7
  •  7
  •   Dennis Williamson    16 年前

    下面是SpoonMeiser的回答,下面是一个具体示例:

    $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
    $
    
        8
  •  7
  •   kolomer    15 年前

    target="$1.$2"
    cur=$(date '+%H.%M')
    while test $target != $cur; do
        sleep 59
        cur=$(date '+%H.%M')
    done
    

    脚本的参数是小时和分钟,因此我可以编写如下内容:

    til 7 45 && mplayer song.ogg
    

    (til是脚本的名称)

    不要再因为你打字错误而迟到了。干杯

        9
  •  4
  •   Ariel    10 年前

    注意“timeToWait”可能是一个负数!(例如,如果指定在“15:57”之前睡觉,现在是“15:58”)。因此,您必须检查它以避免出现奇怪的消息错误:

    #!/bin/bash
    set -o nounset
    
    ### // Sleep until some date/time. 
    # // Example: sleepuntil 15:57; kdialog --msgbox "Backup needs to be done."
    
    
    error() {
      echo "$@" >&2
      exit 1;
    }
    
    NAME_PROGRAM=$(basename "$0")
    
    if [[ $# != 1 ]]; then
         error "ERROR: program \"$NAME_PROGRAM\" needs 1 parameter and it has received: $#." 
    fi
    
    
    current=$(date +%s.%N)
    target=$(date -d "$1" +%s.%N)
    
    seconds=$(echo "scale=9; $target - $current" | bc)
    
    signchar=${seconds:0:1}
    if [ "$signchar" = "-" ]; then
         error "You need to specify in a different way the moment in which this program has to finish, probably indicating the day and the hour like in this example: $NAME_PROGRAM \"2009/12/30 10:57\"."
    fi
    
    sleep "$seconds"
    
    # // End of file
    
        10
  •  2
  •   user8856565 user8856565    7 年前

    https://tamentis.com/projects/sleepuntil/ 正是为了这个目的。这有点过分了,大多数代码来自BSD'at',因此它相当符合标准:

    $ sleepuntil noon && sendmail something
    
        11
  •  1
  •   Tim Frey    16 年前

    您可以计算从现在到唤醒时间之间的秒数,并使用现有的“sleep”命令。

        12
  •  1
  •   Kim Reece    16 年前

    您也许可以使用“at”向您的脚本发送一个信号,脚本正在等待该信号。

        13
  •  0
  •   Dima Tisnek    12 年前

    我刚才写了一些东西来同步多个测试客户端:

    #!/usr/bin/python
    import time
    import sys
    
    now = time.time()
    mod = float(sys.argv[1])
    until = now - now % mod + mod
    print "sleeping until", until
    
    while True:
        delta = until - time.time()
        if delta <= 0:
            print "done sleeping ", time.time()
            break
        time.sleep(delta / 2)
    

    一个简单的用例是运行 ./sleep.py 10; ./test_client1.py ./sleep.py 10; ./test_client2.py 另一方面。

        14
  •  0
  •   Nick Constantine    8 年前
     function sleepuntil() {
      local target_time="$1"
      today=$(date +"%m/%d/%Y")
      current_epoch=$(date +%s)
      target_epoch=$(date -d "$today $target_time" +%s)
      sleep_seconds=$(( $target_epoch - $current_epoch ))
    
      sleep $sleep_seconds
    }
    
    target_time="11:59"; sleepuntil $target_time
    
        15
  •  0
  •   Kris Foster    8 年前

    Hypnos 这样做。在此之前,它使用crontab语法和块进行配置。

    #!/bin/bash
    while [ 1 ]; do
      hypnos "0 * * * *"
      echo "running some tasks..."
      # ...
    done
    
        16
  •  0
  •   NVRM    5 年前

    为了扩展主要答案,下面是一些关于日期字符串操作的有效示例:

    sleep $(($(date -f - +%s- <<< $'+3 seconds\nnow')0)) && ls
    
    sleep $(($(date -f - +%s- <<< $'3 seconds\nnow')0)) && ls
    
    sleep $(($(date -f - +%s- <<< $'3 second\nnow')0)) && ls
    
    sleep $(($(date -f - +%s- <<< $'+2 minute\nnow')0)) && ls
    
    sleep $(($(date -f - +%s- <<< $'tomorrow\nnow')0)) && ls
    
    sleep $(($(date -f - +%s- <<< $'tomorrow 21:30\nnow')0)) && ls
    
    sleep $(($(date -f - +%s- <<< $'3 weeks\nnow')0)) && ls
    
    sleep $(($(date -f - +%s- <<< $'3 week\nnow')0)) && ls
    
    sleep $(($(date -f - +%s- <<< $'next Friday 09:00\nnow')0)) && ls
    
    sleep $(($(date -f - +%s- <<< $'2027-01-01 00:00:01 UTC +5 hours\nnow')0)) && ls
    
        17
  •  0
  •   Janne    3 年前

    也考虑一下这个简洁的BASH:

    sleep $((`date -d "22:00" +%s`-`date  +%s`))
    

    这是一个Bash v4.4.20(1)算术表达式,由两个日期命令替换组成。第一个date命令以秒为单位输出目标时间(自1970年起),后一个命令输出相应的当前时间。这两个数字之间用减号隔开,用于算术运算。因此,可以将秒数作为sleep命令的参数。

        18
  •  -1
  •   cnst    9 年前

    在…上 OpenBSD ,以下内容可用于压缩 */5 5分钟 crontab(5) 把工作变成工作 00 每小时一封(确保生成更少的电子邮件,同时以精确的间隔执行相同的任务):

    #!/bin/sh -x
    for k in $(jot 12 00 55)
      do
      echo $(date) doing stuff
      sleep $(expr $(date -j +%s $(printf %02d $(expr $k + 5))) - $(date -j +%s))
    done
    

    请注意 date(1) 也会打破僵局 sleep(1) 60 分钟不是一个有效的时间(除非是!),因此我们不必在收到电子邮件报告之前等待任何额外的时间。

    sleep 同样,如果你故意不睡觉,你也会失败(因为一个负数被理解为一个命令行选项,而不是一直到下一个小时,甚至是永远),从而确保你的工作仍然可以在分配的时间内完成(例如,如果只有一次迭代需要5分钟多一点的时间,那么我们仍然有时间赶上进度,而不需要在下一个小时做任何事情)。

    这个 printf(1) 因为 date 分钟规格只需要两位数字。

        19
  •  -2
  •   metaphyze    5 年前

    使用柏油。这是我写的专门用来做这件事的工具。

    https://github.com/metaphyze/tarry/

    这是一个简单的命令行工具,用于等待特定时间。这与等待一段时间的“睡眠”不同。如果您希望在特定时间执行某些操作,或者更可能在完全相同的时间执行多个操作,例如测试服务器是否可以处理多个同时发生的请求,那么这将非常有用。在Linux、Mac或Windows上,您可以将其与“&”一起使用:

       tarry -until=16:03:04 && someOtherCommand
    

       for request in 1 2 3 4 5 6 7 8 9 10
       do
           tarry -until=16:03:04 && date > results.$request &
       done
    

    Ubuntu、Linux和Windows二进制文件可以通过页面上的链接获得。