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

时间:2009-03-14 14:36:03

标签: bash sleep wait

我希望我的bash脚本睡到特定时间。所以,我想要一个像“睡眠”这样的命令,它不会占用间隔,而是一个结束时间,直到那时才会睡觉。

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

有这样的命令吗?

17 个答案:

答案 0 :(得分:81)

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

要在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

答案 1 :(得分:19)

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

全部完成!

答案 2 :(得分:13)

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

一般方法

为了减少forks,而不是两次运行date,我更喜欢使用此功能:

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

tomorrow 21:30可以被date未来识别的任何日期和格式替换。

或更好,如果可能的话,今天到达 next HH:MM ,明天如果太晚:

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

这适用于和其他现代shell,但您必须使用:

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

方式(无分叉)

由于我对此类工具的需求从未超过24小时,因此以下仅在未来24小时内涉及HHHH:MMHH:MM:SS语法含义 。 (无论如何,如果你需要更多,你甚至可以使用fork到date返回旧方法。试图在运行多天的脚本中消除一个fork是过度的。)

新版本的bash确实提供printf选项来检索日期,这种新方式 睡觉直到HH:MM 不使用{{1或者任何其他fork,我已经构建了一个小函数。这是:

date

然后:

sleepUntil() {
    local slp tzoff now quiet=false
    [ "$1" = "-q" ] && shift && quiet=true
    local 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))
    $quiet || printf 'sleep %ss, -> %(%c)T\n' $slp $((now+slp))
    sleep $slp
}

HiRes在GNU / Linux

下与的时间

在最近的Linux内核中,您将找到一个名为sleepUntil 11:11 ; date +"Now, it is: %T" sleep 3s, -> sam 28 sep 2013 11:11:00 CEST Now, it is: 11:11:00 sleepUntil -q 11:11:5 ; date +"Now, it is: %T" Now, it is: 11:11:05 的变量文件,您可以在纳秒中读取/proc/timer_listoffset变量。因此,我们可以计算睡眠时间,以达到非常顶级所需的时间。

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

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以计算睡眠时间:< / p>

/proc/timer_list

最后

小测试功能

sleepUntilHires 15:03 ;date +%F-%T.%N ;sleep .97;date +%F-%T.%N
sleep 19.632345552s, -> sam 28 sep 2013 15:03:00 CEST
2013-09-28-15:03:00.003471143
2013-09-28-15:03:00.976100517

sleepUntilHires -q 15:04;date -f - +%F-%T.%N < <(echo now;sleep .97;echo now)
2013-09-28-15:04:00.003608002
2013-09-28-15:04:00.974066555

可能会呈现如下内容:

tstSleepUntilHires () { 
    local now next last
    printf -v now "%(%s)T"
    printf -v next "%(%H:%M:%S)T" $((now+1))
    printf -v last "%(%H:%M:%S)T" $((now+2))
    sleepUntilHires $next
    date -f - +%F-%T.%N < <(echo now;sleep .92;echo now)
    sleepUntilHires $last
    date +%F-%T.%N
}
  • 下一秒开始,
  • 打印时间,然后
  • 等待0.92秒,然后
  • 打印时间,然后
  • 计算剩余0.07秒,到下一秒
  • 睡眠0.07秒,然后
  • 打印时间。

Nota:sleep 0.155579469s, -> Mon Aug 20 20:42:51 2018 2018-08-20-20:42:51.005743047 2018-08-20-20:42:51.927112981 sleep 0.071764300s, -> Mon Aug 20 20:42:52 2018 2018-08-20-20:42:52.003165816 (在我的桌面上)

答案 3 :(得分:8)

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

所以你可以通过发送SIGSTOP来停止你的脚本:

kill -SIGSTOP <pid>

然后使用at deamon通过以相同方式发送SIGCONT来唤醒它。

据推测,你的剧本会告诉你什么时候想要在醒来之前被唤醒。

答案 4 :(得分:6)

关注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
$

答案 5 :(得分:6)

在Ubuntu 12.04.4上,这里的LTS是简单的bash输入:

sleep $(expr `date -d "03/21/2014 12:30" +%s` - `date +%s`)

答案 6 :(得分:4)

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

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是脚本的名称)

上班迟到的日子不会因为你输错了这一天。干杯!

答案 7 :(得分:4)

这是一个完成工作的解决方案,并告知用户剩余的时间。 我几乎每天都在使用它来运行脚本(使用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 :(得分:3)

  

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

答案 9 :(得分:1)

您也许可以使用'at'向您的脚本发送信号,该脚本等待该信号。

答案 10 :(得分:1)

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

答案 11 :(得分:1)

 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

答案 12 :(得分:1)

我整理了一个名为Hypnos的小工具来完成此操作。它使用crontab语法进行配置,直到那时为止。

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

答案 13 :(得分: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

答案 14 :(得分:0)

为了这个目的,我实际上写了https://tamentis.com/projects/sleepuntil/。它有点过度杀死大部分代码来自BSD&#39; at&#39;所以它符合标准:

$ sleepuntil noon && sendmail something

答案 15 :(得分: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

答案 16 :(得分:-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二进制文件可通过页面上的链接获得。

相关问题