示例编辑:2020 年 4 月 22 日星期三,10:30 到 10:55 之间
(阅读示例很重要)
通用方法(避免无用的分叉!)
正如 4 年前提出的这个问题,第一部分涉及 旧 bash 版本:
(注意:此方法使用date -f,它不是 POSIX,在 MacOS 下不起作用!如果在 Mac 下,转到我的 纯bash 函数)
为了减少forks,而不是运行date两次,我更喜欢用这个:
简单的起始样本
sleep $(( $(date -f - +%s- <<< "tomorrow 21:30"$'\nnow') 0 ))
tomorrow 21:30 将来可以被date 识别的任何日期和格式替换。
if read -rp "Sleep until: " targetTime ;then
sleep $(( $(date -f - +%s- <<< "$targetTime"$'\nnow') 0 ))
fi
高精度(纳秒)
几乎相同:
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))
这适用于bash、ksh 和其他现代shell,但你必须使用:
sleep $(( ( $(printf 'tomorrow 21:30\nnow\n' | date -f - +%s-)0 )%86400 ))
在ash 或dash 等lighter shell 下。
纯 bash 方式,没有叉子!!
在 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 或任何其他叉子,我构建了一个小bash 函数。这里是:
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 下使用 bash 的 HiRes 时间
最近的 bash,从 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 的旧方法,普通用户避免使用!!
在 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!
阅读我的warning about difference between $EPOCHSECOND and $EPOCHREALTIME
这个函数使用$EPOCHREALTIME所以不要使用$EPOCHSECOND来建立下一秒:
示例问题:尝试打印下一个四舍五入的时间:
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