bash 睡眠到特定时间/日期

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/645992/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-09 18:02:08  来源:igfitidea点击:

Sleep until a specific time/date

bashsleepwait

提问by theomega

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.

我希望我的 bash 脚本休眠到特定时间。所以,我想要一个像“sleep”这样的命令,它不需要间隔,而是一个结束时间,然后一直睡觉。

The "at"-daemon is not a solution, as I need to block a running script until a certain date/time.

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

Is there such a command?

有这样的命令吗?

回答by SpoonMeiser

As mentioned by Outlaw Programmer, I think the solution is just to sleep for the correct number of seconds.

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

To do this in bash, do the following:

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

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 coreutilsfrom brewinstead → see these instructions

请注意,macOS / OS X 不支持低于秒的精度,您需要使用coreutilsfrom brew请参阅这些说明

回答by John Feminella

Use sleep, but compute the time using date. You'll want to use date -dfor this. For example, let's say you wanted to wait until next week:

使用sleep,但使用计算时间date。你会想用date -d这个。例如,假设您想等到下周:

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

Just substitute "next week" with whatever date you'd like to wait for, then assign this expression to a value, and sleep for that many seconds:

只需将“下周”替换为您想要等待的任何日期,然后将此表达式分配给一个值,然后休眠几秒钟:

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

All done!

全部完成!

回答by F. Hauri

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

由于这个问题是 4 年前提出的,因此第一部分涉及旧的bash 版本:

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

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

General method

一般方法

(Nota: this method use date -fwich is no POSIX and don't work under MacOS! If under Mac, goto my pure bashfunction)

(注意:这个方法使用date -f的不是 POSIX 并且在 MacOS 下不起作用!如果在 Mac 下,请转到我的bash函数

In order to reduce forks, instead of running datetwo times, I prefer to use this:

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

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

where tomorrow 21:30could be replaced by any kind of date and format recognized by date, in the future.

wheretomorrow 21:30将来可以被 , 识别的任何类型的日期和格式替换date

or better, for reaching next HH:MMmeaning today if possible, tomorrow if too late:

或者更好,如果可能的话,今天就达到下一个HH:MM意思,如果太晚了,明天:

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

This works under bash, kshand other modern shells, but you have to use:

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

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

under lightershells like ashor dash.

ashdash较轻的外壳下。

New purebashway, no fork!!

新的bash方式,没有分叉!!

I wrote onetwo little functions: sleepUntiland sleepUntilHires

我写了一个两个小函数: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)

As new versions of bash do offer a printfoption to retrieve date, for this new way to sleep until HH:MMwhithout using dateor any other fork, I've build a little bashfunction. Here it is:

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

sleepUntil() { # args [-q] <HH[:MM[:SS]]> [more days]
    local slp tzoff now quiet=false
    [ "" = "-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 beforethis 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 bashunder GNU/Linux

在 GNU/Linux 下使用bash 的HiRes 时间

Recent bash, from version 5.0 add new $EPOCHREALTIMEvariable with microseconds. From this there is a sleepUntilHiresfunction.

最近的bash,从 5.0 版开始添加新的$EPOCHREALTIME微秒变量。由此有一个sleepUntilHires功能。

sleepUntilHires () { # args [-q] <HH[:MM[:SS]]> [more days]
    local slp tzoff now quiet=false musec musleep;
    [ "" = "-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 -twich is built-in, instead of sleep. Unfortunely, this won't work when running in background, without real TTY. Feel free to replace read -tby sleepif you plan to run this in background scripts... (But for background process, consider using cronand/or atinstead of all this)

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

Skip next paragraph for tests and warning about $?POCHSECONDS!

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

Recent kernel avoid using /proc/timer_listby user!!

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

Under recent Linux kernel, you will find a variables file named /proc/timer_listwhere you could read an offsetand a nowvariable, in nanoseconds. So we may compute sleep time to reach the very topdesired time.

在最近的 Linux 内核下,您会找到一个变量文件,命名为/proc/timer_list您可以在其中读取 anoffset和一个now变量,以nanoseconds 为单位。因此,我们可以计算睡眠时间达到最高层需要的时间。

(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
    [ "" = "-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-onlyvariables, TIMER_LIST_OFFSETand TIMER_LIST_SKIP, the function will access very quickly the variable file /proc/timer_listfor computing sleep time:

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

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
    [ "" = "-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计算睡眠时间:

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.
  • 下一秒开始时,
  • 打印时间,然后
  • 等待 0.92 秒,然后
  • 打印时间,然后
  • 计算还剩 0.07 秒,到下一秒
  • 睡眠 0.07 秒,然后
  • 打印时间。

Care to not mix $EPOCHSECONDand $EPOCHREALTIME!

注意不要混合$EPOCHSECOND$EPOCHREALTIME

Read my warning about difference between $EPOCHSECONDand $EPOCHREALTIME

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

This function use $EPOCHREALTIMEso don't use $EPOCHSECONDfor establishing next second:

此函数使用$EPOCHREALTIME所以不要$EPOCHSECOND用于创建下一秒

Sample issue: Trying to print time next rounded by 2 seconds:

示例问题:尝试打印下一个四舍五入 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

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

回答by Camusensei

Here is a solution that does the job AND informs the user about how much time is remaining. I use it almost everyday to run scripts during the night (using cygwin, as I couldn't get cronto work on windows)

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

Features

特征

  • Precise down to the second
  • Detects system time changes and adapts
  • Intelligent output telling how much time is left
  • 24-hour input format
  • returns true to be able to chain with &&
  • 精确到第二
  • 检测系统时间变化并适应
  • 智能输出告诉你还剩多少时间
  • 24小时输入格式
  • 返回 true 以便能够链接 &&

Sample run

样品运行

$ 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

(The date at the end is not part of the function, but due to the && date)

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

Code

代码

til(){
  local hour mins target now left initial sleft correction m sec h hm hs ms ss showSeconds toSleep
  showSeconds=true
  [[  =~ ([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 "" && 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
}

回答by SpoonMeiser

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.

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

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

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

kill -SIGSTOP <pid>

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

然后使用 at 守护进程通过以相同方式向其发送 SIGCONT 来唤醒它。

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

据推测,您的脚本会在它自己进入睡眠状态之前通知它什么时候想要被唤醒。

回答by Anthony O.

On Ubuntu 12.04.4 LTS here is the simple bash input which works :

在 Ubuntu 12.04.4 LTS 上,这里是简单的 bash 输入,它可以工作:

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

回答by Paused until further notice.

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

按照 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
$

回答by kolomer

I wanted an script that only checked the hours and minutes so I could run the script with the same parameters every day. I don't want to worry about which day will be tomorrow. So I used a different approach.

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

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

the parameters to the script are the hours and minutes, so I can write something like:

脚本的参数是小时和分钟,所以我可以这样写:

til 7 45 && mplayer song.ogg

(til is the name of the script)

(til 是脚本的名称)

no more days late at work cause you mistyped the day. cheers!

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

回答by kolomer

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

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

Beware that "timeToWait" could be a negative number! (for example, if you specify to sleep until "15:57" and now it's "15:58"). So you have to check it to avoid strange message errors:

请注意“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 "##代码##")

if [[ $# != 1 ]]; then
     error "ERROR: program \"$NAME_PROGRAM\" needs 1 parameter and it has received: $#." 
fi


current=$(date +%s.%N)
target=$(date -d "" +%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

回答by Outlaw Programmer

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

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