Bash:睡到特定的时间/date
我想我的bash脚本睡觉,直到特定的时间。 所以,我想要一个像“睡眠”一样的命令,它不需要间隔,而是一个结束时间,然后睡觉。
“at”守护进程不是解决scheme,因为我需要阻止正在运行的脚本,直到某个date/时间。
有这样的命令吗?
正如“非法程序员”所提到的那样,我认为解决scheme就是睡觉正确的秒数。
要在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不支持秒以下的精度,您需要从brew
使用coreutils
→ 请参阅这些说明
使用sleep
,但使用date
来计算时间。 你会想用这个date -d
。 例如,假设您想等到下周:
expr `date -d "next week" +%s` - `date -d "now" +%s`
只要用你希望等待的date来替代“下周”,然后把这个expression式赋值给一个值,然后睡几秒钟:
startTime=$(date +%s) endTime=$(date -d "next week" +%s) timeToWait=$(($endTime- $startTime)) sleep $timeToWait
全做完了!
您可以通过发送一个SIGSTOP信号来阻止进程执行,然后通过发送一个SIGCONT信号来让它恢复执行。
所以你可以通过发送一个SIGSTOP来停止你的脚本:
kill -SIGSTOP <pid>
然后使用atamon以同样的方式发送一个SIGCONT来唤醒它。
据推测,你的脚本会告诉什么时候它想睡觉之前被唤醒。
正如4年前问这个问题,第一部分涉及老 bash版本:
一般方法
为了减lessforks
,而不是两次运行date
,我更喜欢使用这个:
sleep $(($(date -f - +%s- <<< $'tomorrow 21:30\nnow')0))
tomorrow 21:30
可以用date确认的任何一种date和格式来代替。
或者更好,如果可能的话,今天就到达下一个HH:MM
,明天如果太迟:
sleep $((($(date -f - +%s- <<<$'21:30 tomorrow\nnow')0)%86400))
这个工作在bash , ksh等现代shell下,但是你必须使用:
sleep $(( ( $(printf 'tomorrow 21:30\nnow\n' | date -f - +%s-)0 )%86400 ))
如果在较轻的壳如灰或短跑 。
新的bash方式(无叉)
由于我对这种工具的需求从未超过24小时,因此以下仅涉及HH
, HH:MM
或HH:MM:SS
在未来24小时内的语法含义。 (无论如何,如果你需要更多的东西,你甚至可以使用fork来返回旧的方法,试图消除一个运行了多天的脚本中的一个fork是矫枉过正的。)
由于新版本的bash提供了一个printf
选项来检索date,为了这个新的睡眠方式, 直到HH:MM没有使用date
或任何其他分支,我已经构build了一个小小的bash函数,它是:
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 }
比:
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
在GNU / Linux下的高分辨率时间
在最近的Linux内核下,你会发现一个名为/proc/timer_list
的variables文件,你可以在这里读取一个offset
和一个now
variables,以毫微秒为单位。 所以我们可以计算睡眠时间以达到最佳的理想时间。
(我写这个来生成和跟踪非常大的日志文件上的特定事件,包含千行一秒)。
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]//[az.: ]} && \ 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//[az ]}+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 }
在定义了两个只读variables: TIMER_LIST_OFFSET
和TIMER_LIST_SKIP
,函数将非常快速地访问variables文件/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
最后
sleepUntilHires -q 15:11;date -f - +%F-%T.%N < <(echo now;sleep .97;echo now);sleepUntilHires 15:12;date +%F-%T.%N 2013-09-28-15:11:00.003973984 2013-09-28-15:11:00.974306364 sleep 59.024430416s, -> sam 28 sep 2013 15:12:00 CEST 2013-09-28-15:12:00.003447890
在那里, 整整一分钟,有:
.97秒(睡眠) – 59.024430416秒(睡眠)= .005569584
date
,还不到1/100秒,读取计时器和计算睡眠时间。
sleepUntilHires -q 16:16;date -f - +%F-%T.%N < <(echo now;sleep .97;echo now);sleepUntilHires 16:16:01;date +%F-%T.%N 2013-09-28-16:16:00.003657646 2013-09-28-16:16:00.974469831 sleep 0.024204370s, -> Sat Sep 28 16:16:01 2013 2013-09-28-16:16:01.003133202
跟随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 $
我想要一个脚本,只检查小时和分钟,所以我可以每天使用相同的参数运行脚本。 我不想担心明天是哪一天。 所以我用了一个不同的方法。
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
(直到是脚本的名字)
迟到的工作没有更多的日子,因为你打错了一天。 干杯!
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
这是一个解决scheme,并通知用户剩余的时间。 我几乎每天都使用它来在夜间运行脚本(使用cygwin,因为我不能让cron
在windows上工作)
特征
- 精确到第二
- 检测系统时间变化并适应
- 智能输出告诉剩下多less时间
- 24小时input格式
- 返回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不是函数的一部分,但是由于&& 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 }
您可以计算从现在到唤醒时间之间的秒数,并使用现有的“睡眠”命令。
在Ubuntu 12.04.4 LTS这里是简单的bashinput工作:
sleep $(expr `date -d "03/21/2014 12:30" +%s` - `date +%s`)
你也许可以用'at'向你的脚本发送一个信号,它正在等待那个信号。
我把一个名叫Hypnos的小公司放在一起做这件事。 它使用crontab语法进行configuration,并在此之前阻塞。
#!/bin/bash while [ 1 ]; do hypnos "0 * * * *" echo "running some tasks..." # ... done
下面是我刚刚写的同步多个testing客户端的东西:
#!/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_client1.py
在一个terminal和./sleep.py 10; ./test_client2.py
./sleep.py 10; ./test_client2.py
在另一个。
在OpenBSD上 ,可以使用以下命令将*/5
5分钟crontab(5)
作业压缩到每小时00
小时(以确保生成更less的电子邮件,同时每隔一段时间执行相同的任务):
#!/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)
也会在最后的迭代中通过devise破坏sleep(1)
,因为60
分钟不是有效的时间(除非是!),因此我们不必等待任何额外的时间得到我们的电子邮件
还要注意,如果其中一个迭代需要超过5分钟的时间,那么sleep
同样会因为完全不睡觉而在devise上失败(由于负数被解释为命令行选项,而不是缠绕在周围到下一个小时,甚至是永恒),从而确保你的工作可以在分配的时间内完成(例如,如果只有一个迭代需要超过5分钟,那么我们仍然有时间赶上,没有任何东西包装到下一个小时)。
printf(1)
是需要的,因为date
需要精确的两位数的分钟规格。
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
实际上我写了https://tamentis.com/projects/sleepuntil/ 。 这有点过分杀死了大部分代码来自BSD'at',所以它是相当标准的:
$ sleepuntil noon && sendmail something