我希望我的bash脚本在特定时间之前保持睡眠状态。因此,我想要一个像“ sleep”这样的命令,该命令不需要间隔,而是要有一个结束时间,然后一直睡眠到那时。
“ at”守护程序不是解决方案,因为我需要阻止正在运行的脚本,直到某个日期/时间为止。
有这样的命令吗?
Answers:
正如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不支持秒级以下的精度,您需要改用coreutils
from,brew
→ 请参阅以下说明
target_epoch=$(date -d 'tomorrow 03:00' +%s)
改用。
正如4年前提出的这个问题,第一部分涉及旧的bash版本:
上次编辑:2020年4月22日,星期三,大约10:30到10h:55之间的时间(阅读样本很重要)
(注意:此方法使用date -f
wich并非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测试!
我写了两个小功能: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不使用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
最近 重击,从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 -t
的sleep
,如果你打算在后台脚本运行这个......(但是,对于后台进程,可以考虑使用cron
和/或at
所有这代替)
跳过下一段进行测试和警告$ËPOCHSECONDS
!
/proc/timer_list
被用户使用!(我编写此代码是为了在非常大的日志文件中生成和跟踪特定事件,该日志文件在一秒钟内包含千行)。
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
$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
使用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
全部做完!
-d
是迄今为止的非POSIX扩展。在FreeBSD上,它尝试设置内核的夏令时值。
这是一种解决方案,可以完成工作并通知用户剩余时间。我几乎每天都在晚上使用它来运行脚本(使用cygwin,因为我无法cron
在Windows上工作)
&&
$ 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
}
您可以通过发送SIGSTOP信号来停止进程执行,然后通过发送SIGCONT信号使其恢复执行。
因此,您可以通过发送SIGSTOP来停止脚本:
kill -SIGSTOP <pid>
然后使用at deamon通过相同的方式将其发送给SIGCONT将其唤醒。
据推测,您的脚本会在进入睡眠状态之前通知您何时需要被唤醒。
在Ubuntu 12.04.4 LTS上,这是可以正常工作的简单bash输入:
sleep $(expr `date -d "03/21/2014 12:30" +%s` - `date +%s`)
为了遵循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
$
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
SIGNCHAR=${SECONDS:0:1}
,然后再获得if [ "$SIGNCHAR" = "-" ]
。那应该做。
您也许可以使用“ at”向脚本发送信号,脚本等待该信号。
我实际上是出于这个确切的目的写了https://tamentis.com/projects/sleepuntil/。大部分代码都来自BSD at,因此有点过分杀手,因此相当符合标准:
$ sleepuntil noon && sendmail something
这是我刚才写的用于同步多个测试客户端的内容:
#!/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
另一个终端中运行。
until
可以设置为特定的日期/时间
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
我组合了一个名为Hypnos的小型实用程序来执行此操作。它使用crontab语法进行配置,并且在此之前一直处于阻止状态。
#!/bin/bash
while [ 1 ]; do
hypnos "0 * * * *"
echo "running some tasks..."
# ...
done
为了扩展主要答案,这是一些有关日期字符串操作的有效示例:
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
在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
分钟不是有效时间(除非是分钟!),因此在获取电子邮件报告之前,我们无需等待任何额外的时间。
另请注意,如果其中一个迭代分配了5分钟以上的时间, sleep
同样由于设计原因,它根本不会休眠会很友好地失败(由于负数被解释为命令行选项,而不是绕回去)接下来的一个小时甚至是永恒的时间),从而确保您的工作仍能在分配的小时内完成(例如,如果仅一次迭代花费了超过5分钟的时间,那么我们仍然有时间赶上,而无需到下一小时的所有内容)。
printf(1)
之所以需要,是因为date
分钟说明需要精确两位数。
使用柏油。这是我为此专门编写的工具。
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二进制文件。