睡到特定的时间/日期


89

我希望我的bash脚本在特定时间之前保持睡眠状态。因此,我想要一个像“ sleep”这样的命令,该命令不需要间隔,而是要有一个结束时间,然后一直睡眠到那时。

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

有这样的命令吗?


1
请注意,仅在计算之前使用长时间睡眠的解决方案可能会睡眠太久,甚至可能睡得太久,特别是如果您的计算机可以进入休眠状态。posix sleep命令不能保证不要睡得太久。@Camusensei的解决方案很好地解决了这个问题。
GregD

Answers:


98

正如Outlaw Programmer所提到的,我认为解决方案是使睡眠正确的秒数。

为此,请执行以下操作:

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不支持秒级以下的精度,您需要改用coreutilsfrom,brew请参阅以下说明


8
为此,我编写了一个小的shell脚本来获取“ sleepuntil”命令。
theomega

1
@enigmaticPhysicist:有一个类似的命令调用“ at”,它异步运行。
voidlogic

6
如果您想明天凌晨3点停下来,可以target_epoch=$(date -d 'tomorrow 03:00' +%s)改用。
Yajo

您可以使用一个叉子date代替两个叉子来做同样的事情!请参阅我的答案的
F. Hauri,

1
请注意,此解决方案可能会睡得太久,而且如果您的计算机休眠了一段时间,它可能会太长。
GregD

23

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

上次编辑:2020年4月22日,星期三,大约10:30到10h:55之间的时间(阅读样本很重要)

一般方法(避免使用无用的分叉!)

(注意:此方法使用date -fwich并非POSIX,在MacOS上不起作用!如果在Mac上,请转到我的功能

为了减少forks,而不是运行date两次,我更喜欢使用以下代码:

简单的起始样本

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

其中,tomorrow 21:30可以通过任何一种方式和格式由公认的替代date,在未来。

高精度(纳秒级)

几乎相同:

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 ))

在像这样的壳下 要么

方式,没有叉子!!

经过MacOS测试!

我写两个小功能:sleepUntilsleepUntilHires

 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不使用date或使用任何其他叉子,我已经做了一些功能。这里是:

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功能。

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是内置的,而不是sleep。不幸的是,如果没有真正的TTY,这在后台运行时将无法工作。随意更换read -tsleep,如果你打算在后台脚本运行这个......(但是,对于后台进程,可以考虑使用cron和/或at所有这代替)

跳过下一段进行测试和警告$ËPOCHSECONDS

最近的内核避免/proc/timer_list被用户使用!

在最新的Linux内核下,您会发现一个名为`/ proc / timer_list`的变量文件,您可以在纳秒内读取一个`offset'和一个'now'变量。因此,我们可以计算睡眠时间以达到“非常高”的期望时间。

(我编写此代码是为了在非常大的日志文件中生成和跟踪特定事件,该日志文件在一秒钟内包含千行)。

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,该函数将非常快速地访问变量文件/proc/timer_list以计算睡眠时间:

测试功能小

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.92秒,然后
  • 然后打印时间
  • 计算剩余的0.07秒,直到下一秒
  • 睡0.07秒,然后
  • 打印时间。

注意不要混用$EPOCHSECOND$EPOCHREALTIME

阅读我关于$EPOCHSECOND和之间差异的警告$EPOCHREALTIME

此函数使用,$EPOCHREALTIME因此不要$EPOCHSECOND用于建立下一秒

示例问题:尝试将打印时间下舍入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

1
哇!在bash下计算纳秒
F. Hauri

在本地bash v5 +下计算微秒!
F. Hauri

21

使用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

全部做完!


值得扩展如何将值分配给变量,因为timeToWait = expr ...无法直接工作,并且不能使用反引号,因为它们不会嵌套,因此必须使用$ ()或临时变量。
SpoonMeiser 2009年

好建议;我将修改我的内容以使其更清晰,以免人们误解。
John Feminella 09年

注意-d是迄今为止的非POSIX扩展。在FreeBSD上,它尝试设置内核的夏令时值。
Dave C

9

这是一种解决方案,可以完成工作并通知用户剩余时间。我几乎每天都在晚上使用它来运行脚本(使用cygwin,因为我无法cron在Windows上工作)

特征

  • 精确到秒
  • 检测系统时间变化并适应
  • 智能输出告诉您还剩多少时间
  • 24小时输入格式
  • 返回true以能够与 &&

样品运行

$ 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
}

8

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

因此,您可以通过发送SIGSTOP来停止脚本:

kill -SIGSTOP <pid>

然后使用at deamon通过相同的方式将其发送给SIGCONT将其唤醒。

据推测,您的脚本会在进入睡眠状态之前通知您何时需要被唤醒。



7

为了遵循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
$

我认为您想调度一个SIGCONT而不是另一个SIGSTOP,因此信号或注释错误。否则,很高兴看到一个适当的示例。
SpoonMeiser

糟糕,我在评论中打错了字。现在已修复。但是您必须使用数字信号观看,因为它们可能会有所不同。
暂停,直到另行通知。

我发现破折号似乎不理解SIGCONT,所以起初我使用-18(不可移植),然后发现它喜欢CONT,因此我编辑了上面的答案以解决此问题。
暂停,直到另行通知。

6

我想要一个只检查小时和分钟的脚本,这样我每天都可以使用相同的参数运行该脚本。我不想担心明天是哪一天。所以我使用了不同的方法。

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

(直到脚本的名称)

上班时间不再延迟,因为您输错了一天。干杯!


4

timeToWait = $((($ end-$ start))

注意“ 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

不过,好的解决方案是,我认为$ SECONDS是一个字符串,因此防御性代码应类似于先获得子字符串SIGNCHAR=${SECONDS:0:1},然后再获得if [ "$SIGNCHAR" = "-" ]。那应该做。
H2ONaCl 2014年

1

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




0

这是我刚才写的用于同步多个测试客户端的内容:

#!/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另一个终端中运行。


我的问题与哪里有联系?
theomega 2013年

它很容易扩展,因此until可以设置为特定的日期/时间
Dima Tisnek 2013年

0
 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

您可以在此解决方案中添加某种说明吗?
统治

这是一个bash脚本;sleepuntil函数接受一个参数;您可以设置变量target_time =“ 11:59”或您想要休眠的任何时间,直到 然后使用target_time参数调用该函数。它计算
尼克·康斯坦丁

0

我组合了一个名为Hypnos的小型实用程序来执行此操作。它使用crontab语法进行配置,并且在此之前一直处于阻止状态。

#!/bin/bash
while [ 1 ]; do
  hypnos "0 * * * *"
  echo "running some tasks..."
  # ...
done

0

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

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

-1

OpenBSD上,可以将以下内容用于将*/55分钟的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分钟不是有效时间(除非是分钟!),因此在获取电子邮件报告之前,我们无需等待任何额外的时间。

另请注意,如果其中一个迭代分配了5分钟以上的时间, sleep同样由于设计原因,它根本不会休眠会很友好地失败(由于负数被解释为命令行选项,而不是绕回去)接下来的一个小时甚至是永恒的时间),从而确保您的工作仍能在分配的小时内完成(例如,如果仅一次迭代花费了超过5分钟的时间,那么我们仍然有时间赶上,而无需到下一小时的所有内容)。

printf(1)之所以需要,是因为date分钟说明需要精确两位数。


-2

使用柏油。这是我为此专门编写的工具。

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

这是一个等待特定时间的简单命令行工具。这与等待一段时间的“睡眠”不同。如果您想在特定时间执行某件事,或者更可能在完全同一时间执行几件事,例如测试服务器是否可以处理多个非常同时的请求,则这很有用。您可以在Linux,Mac或Windows上将其与“ &&”一起使用,例如:

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

这将等到下午4:03:04,然后执行someOtherCommand。这是一个Linux / Mac示例,该示例演示如何运行所有计划在同一时间启动的多个请求:

   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二进制文件。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.