睡眠直到特定的时间/日期

125

我希望我的bash脚本能够睡眠,直到特定的时间。因此,我想要一个像 "sleep" 命令一样的命令,它不需要间隔时间,而是需要一个结束时间,并一直睡眠到该时间。

"at" 守护进程不是解决方案,因为我需要阻止正在运行的脚本直到某个特定日期/时间。

是否有这样的命令?


3
注意,仅使用长时间睡眠加上计算的解决方案可能会睡得太久,特别是如果您的机器可以进入休眠状态。posix睡眠命令不能保证不会睡得太久。@Camusensei提出的解决方案非常妙。 - GregD
@GregD 请看一下我的2023年1月:有关GregD的评论和休眠的新章节 - F. Hauri - Give Up GitHub
22个回答

126

正如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 不支持秒以下的精度,您需要使用 brew 中的 coreutils。如果需要,请查看这些说明


8
谢谢,我写了一个小的 shell 脚本来获取“sleepuntil”命令。 - theomega
1
@enigmaticPhysicist:有一个类似的命令叫做“at”,可以异步运行。 - voidlogic
10
如果你想在明天早上3点停止,你可以使用target_epoch=$(date -d 'tomorrow 03:00' +%s) - Yajo
5
请注意,这种解决方案可能会休眠时间过长,如果您的机器休眠时间太长,它可能会更加久远。 - GregD
1
请注意,sleep 存在一个缺陷 - 它会按照实际机器时间进行休眠。换句话说,它会忽略计算机的睡眠状态(抱歉,这是个双关语)。如果您使用 sleep 休眠 2 分钟,同时将机器置于睡眠(或休眠)状态 1 分钟,则从“人类”时间来看,您的脚本将休眠 3 分钟。准确地说,问题是“休眠直到特定日期/时间”,而不是“休眠一段特定的时间”,因此所有基本 sleep 的答案都不完全正确。 - Oleksandr Boiko
显示剩余3条评论

54

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

一般方法(避免无用的分叉!)

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

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

为了减少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))

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

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

在像, 这样的轻型外壳下(一个管道/分叉更多)。

纯粹的方式,无需分叉!

在MacOS下测试过!

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

随着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' $now
    tzoff=$((0${tzoff:0:1}(3600*10#${tzoff:1:2}+60*10#${tzoff: -2})))
    slp=$(((( 86400 + ( now - now%86400 ) +
                10#$hms*3600+10#${hms[1]:-0}*60+10#${hms[2]:-0} -
                tzoff - now
            ) % 86400 ) + 10#${2:-0} * 86400
          ))
    $quiet || printf 'sleep %ss, -> %(%c)T\n' $slp $((now+slp))
    read -t $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下使用实现高精度时间

最近版本的,从5.0版本开始添加了新的$EPOCHREALTIME变量,支持微秒级别的时间戳。基于此,可以编写sleepUntilHires函数。

sleepUntilHires() { # args: [-q] <HH[:MM[:SS[.xxx]]]> [more days]
    local slp tzoff now quiet=false musec musleep tmu
    [ "$1" = "-q" ] && shift && quiet=true
    local -a hms
    IFS=: read -a hms <<<${1}
    printf -v tmu %.06f ${hms[2]:-0}
    printf -v hms[2] %.0f ${tmu%.*}
    tmu=${tmu#*.}
    printf -v now '%(%s)T' -1
    IFS=. read now musec <<<$EPOCHREALTIME
    musleep=$((2000000+10#$tmu-10#$musec))
    printf -v tzoff '%(%z)T\n' $now
    tzoff=$((0${tzoff:0:1}(3600*10#${tzoff:1:2}+60*10#${tzoff:3:2})))
    slp=$(((( 86400 + ( now - now%86400 ) +
                10#$hms*3600+10#0${hms[1]:-0}*60+10#${hms[2]:-0} -
                tzoff - now - 1
            ) % 86400 ) + 10#${2:-0} * 86400
          )).${musleep:1}
    $quiet ||
        printf 'sleep %ss, -> %(%c)T.%s\n' $slp $((now+${slp%.*}+1)) $tmu
    read -t $slp _
}

请注意:此处使用内置的read -t而非sleep。不幸的是,如果在后台运行且没有真正的TTY,则无法工作。如果您计划在后台脚本中运行,请随意将read -t替换为sleep...(但对于后台进程,请考虑使用cron和/或at代替所有这些)。

高精度睡眠直到下一个周期

关于RonJohn's comment,如果目标是每15分钟启动一次,则sleep命令可以变成:

sleepUntilNext() { # args: [-q] <float period in seconds>
    local slp sec mus quiet=0 res=0 chr
    [[ $1 == -q ]] && quiet=1 && shift
    printf -v slp %.6f $1
    slp=00000$(( 10#${slp/.} - ${EPOCHREALTIME/.} % 10#${slp/.} ))
    printf -v slp %.6f ${slp::-6}.${slp: -6}
    ((quiet)) || printf 'Sleep %s...' $slp >&2
    IFS= read -d '' -rsn1 -t $slp chr &&
        printf -v res %d \'"$chr"
    IFS=. read sec mus <<<$EPOCHREALTIME
    ((quiet)) || {
        IFS=. read sec mus <<<$EPOCHREALTIME
        printf '\rDone: %(%a %d %T)T.%s\e[K\n' $sec $mus >&2
    }
    return $res
}

然后持续15分钟:

sleepUntilNext 900
Sleep 662.334231...
Done: Wed 26 16:15:00.000171

20分钟的时间:

sleepUntilNext 1200
Sleep 11.509587...
Done: Wed 26 16:20:00.000168

但是它接受 浮点数 值作为周期:
while sleepUntilNext -q 1.5;do date +%T.%N;done
16:32:49.502416682
16:32:51.001073971
16:32:52.502804292
16:32:54.002940518
16:32:55.503174242

(将read -t替换为sleep或使用伪文件描述符,请参见有关睡眠和/或read -t的信息)

测试和警告$ËPOCHSECONDS!请跳过下一段。

旧的方法使用/proc/timer_list,被最近的内核避免使用

在Linux内核下,您会发现一个名为/proc/timer_list的变量文件,您可以在其中读取offsetnow变量,以纳秒为单位。因此,我们可以计算睡眠时间以达到所需的非常顶部时间。

(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
    [ "$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
}

After defining two read-only variables, TIMER_LIST_OFFSET and TIMER_LIST_SKIP, the function will access very quickly the variable file /proc/timer_list for computing sleep time:

小测试函数

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.000000
2020-04-22-10:34:39.001685312
2020-04-22-10:34:39.922291769
sleep 0.077012s, -> Wed Apr 22 10:34:40 2020.000000
2020-04-22-10:34:40.004264869
  • 在下一秒开始时,
  • 打印时间,然后
  • 等待0.92秒,然后
  • 打印时间,然后
  • 计算距离下一秒还有0.07秒
  • 睡眠0.07秒,然后
  • 打印时间。

2023年1月:关于GregD的评论休眠的新章节

GregD的评论警告说如果在计算机进入深度睡眠模式之前启动,会导致睡眠时间过长...

为此,我已经重新编写了sleepUntilHires函数:当被唤醒时,它们不会唤醒计算机,而是立即结束(最多20毫秒),并显示时间间隔。

sleepUntilHires() { # args: [-q] <HH[:MM[:SS[.xxx]]]> [more days]
    local slp tzoff now quiet=false musec musleep tmu start=${EPOCHREALTIME} tgt
    [[ $_dummySleepFd ]] && [[ -e /dev/fd/$_dummySleepFd ]] ||
        exec {_dummySleepFd}<> <(:)
    [ "$1" = "-q" ] && shift && quiet=true
    local -a hms; IFS=: read -a hms <<<${1}
    printf -v tmu %.06f ${hms[2]:-0}
    printf -v hms[2] %.0f ${tmu%.*}
    tmu=${tmu#*.}; printf -v now '%(%s)T' -1
    IFS=. read now musec <<<$start
    musleep=$((2000000+10#$tmu-10#$musec))
    printf -v tzoff '%(%z)T\n' $now
    tzoff=$((0${tzoff:0:1}(3600*10#${tzoff:1:2}+60*10#${tzoff:3:2})))
    slp=$(((( 86400 + ( now - now%86400 ) +
                10#$hms*3600+10#0${hms[1]:-0}*60+10#${hms[2]:-0} -
                tzoff - now - 1
            ) % 86400 ) + 10#${2:-0} * 86400
          )).${musleep:1}
    tgt=$((${start/.}+10#${slp/.}))
    printf -v tmu  '%(%c)T.%s' $(((${start/.}+10#${slp/.})/1000000)) $tmu
    while ((slp=tgt-${EPOCHREALTIME/.},slp>0)) ;do
        slp=00000$slp
        printf -v slp %.6f ${slp::-6}.${slp: -6}
        $quiet || printf '\rsleep %ss, -> %s ...' "$slp" "$tmu"
        if (( 10#${slp/.} > 20000));then      # If left more than 20 ms
            read -u $_dummySleepFd -t .02 _   # then sleep 20 ms
        else read -u $_dummySleepFd -t $slp _ # else sleep left time
        fi 
    done
    if ! $quiet;then  now=00000$((${EPOCHREALTIME/.}-tgt))
        printf '\nScript done %.6f seconds late.\n' ${now::-6}.${now: -6}
    fi        
}

好的,我现在得开始启动我的笔记本电脑了...

sleepUntilHires 15:13:18.1234
sleep 0.002148s, -> lun 16 jan 2023 15:13:18 CET.123400 ...
Script done 0.000746 seconds late.

这是一次正常运行。现在我将把我的笔记本电脑置于休眠状态:

sleepUntilHires 15:15:18.1234
sleep 86.256328s, -> lun 16 jan 2023 15:15:18 CET.123400 ...
Script done 111.469551 seconds late.

然后当被唤醒时,立即结束睡眠,并显示他的最终差距。

注意不要混淆$EPOCHSECOND$EPOCHREALTIME

请阅读我的有关$EPOCHSECOND$EPOCHREALTIME之间差异的警告

此函数使用$EPOCHREALTIME,因此不要使用$EPOCHSECOND来确定下一秒

示例问题:尝试打印下一秒四舍五入后的时间:

for i in 1 2;do
    printf -v nextEvenSecond "%(%T)T" $(((EPOCHSECONDS/2)*2+2))
    echo $nextEvenSecond
    sleepUntilHires $nextEvenSecond
    IFS=. read now musec <<<$EPOCHREALTIME
    printf "%(%c)T.%s\n" $now $musec
done

可能会产生:

11:44:32
sleep 1.485212s, -> Wed Nov  9 11:44:32 2022.000000
Wed Nov  9 11:44:32 2022.000311
11:44:32
sleep 86399.999143s, -> Thu Nov 10 11:44:32 2022.000000

你将要等待1天而不是2秒钟!!!

你必须使用$EPOCHREALTIME

    printf -v nextEvenSecond "%(%T)T" $(((${EPOCHREALTIME%.*}/2)*2+2))

11:48:12
sleep 0.300672s, -> Wed Nov  9 11:48:12 2022.000000
Wed Nov  9 11:48:12 2022.000345
11:48:14
sleep 1.998397s, -> Wed Nov  9 11:48:14 2022.000000
Wed Nov  9 11:48:14 2022.000536
11:48:16
sleep 1.998916s, -> Wed Nov  9 11:48:16 2022.000000
Wed Nov  9 11:48:16 2022.000325

关于 sleep 和/或 read -t

在这些函数中,我使用 read -t (超时)替换了 sleep,有两个原因:

  • 这允许用户通过按下 Return 键来中断 sleep.
    (变体:IFS = read -sn 1 -t $slp _,允许用户通过按下任意键来中断)。
  • sleep 隐含调用 (fork) 到 /bin/sleep 因为它不是一个内置的命令。

为了避免键盘交互而使其在非交互式模式下运行,您可以:

  • sleepUntil.source 脚本开头创建一个伪终端(文件描述符)以重定向 read 的输入:
   exec {_dummySleepFd}<> <(:)

然后替换

   read -t $slp _

   read -u $_dummySleepFd -t $slp _
  • 如果您的bash实现允许,请加载sleep 可加载内置(请参见sudo apt install bash-builtins):
   enable -f sleep sleep

如果你的$BASH_LOADABLES_PATH已设置,否则:
   enable -f /usr/lib/bash/sleep sleep

卸载/禁用:

   enable -d sleep

一些在我的树莓派上的测试:

exec {_dummySleepFd}<> <(:)
s=${EPOCHREALTIME/.};read -u $_dummySleepFd -t .01;echo $((${EPOCHREALTIME/.}-s))
10975

s=${EPOCHREALTIME/.};read -u $_dummySleepFd -t .01;echo $((${EPOCHREALTIME/.}-s))
10977

使用 /bin/sleep

s=${EPOCHREALTIME/.};sleep .01;echo $((${EPOCHREALTIME/.}-s))
60676

s=${EPOCHREALTIME/.};sleep .01;echo $((${EPOCHREALTIME/.}-s))
47910

s=${EPOCHREALTIME/.};sleep .01;echo $((${EPOCHREALTIME/.}-s))
44585

第一次,/bin/sleep从文件系统中被加载。虽然这需要更长的时间。但是无论如何,即使在缓存中,运行一个fork来执行sleep也会消耗资源...
enable -f /usr/lib/bash/sleep sleep
s=${EPOCHREALTIME/.};sleep .01;echo $((${EPOCHREALTIME/.}-s))
10803

s=${EPOCHREALTIME/.};sleep .01;echo $((${EPOCHREALTIME/.}-s))
10760

看起来比使用read -u $_dummySleepFd -t ...稍微好一点。


1
哇!在 [tag:bash] 下计算 纳秒 - F. Hauri - Give Up GitHub
1
严谨的注意事项:%86400技巧仅在两个时间不跨越夏令时转换时才有效。 - 200_success
我现在使用sleepUntil函数时出现了新的错误:bash: 0-(3600*08: value too great for base (error token is "08") - hlidka
1
@hlidka:谢谢,修复了关于*TZ*的错误:通过LANG=C TZ=UTC+9 sleepUntil 09:09:09 09LANG=C TZ=UTC+9 sleepUntilHires 09:09:09.009 09进行了测试。 - F. Hauri - Give Up GitHub
1
"Simple starting sample"非常适合我需要在秒针到达0、15、30和45时运行的一些命令。 - RonJohn
显示剩余4条评论

25

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

完成了!


值得深入探讨如何将值分配给变量,因为timeToWait=expr...不能直接工作,而且你不能使用反引号,因为它们不会嵌套,所以你必须使用$()或临时变量。 - SpoonMeiser
好的建议;我会修改我的内容,让它更清晰明了,这样人们就不会产生误解。 - John Feminella
注意:-d 是一个非POSIX扩展的日期命令。在FreeBSD上,它试图设置内核对夏令时的值。 - Dave C

16

这是一个简单的Bash一行命令:

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

这正是我正在寻找的解决方案。非常简洁。 - Aria
无法在Ubuntu 20.04.2 LTS上工作。我收到一个错误 sleep: invalid option -- '2' - Jan
3
@Jan 是的,它仍然有效,我刚刚测试过了。当您在“-d”参数后将日期以字符串形式提供且此日期早于当前系统日期时,就会显示此消息。为了显示系统的当前日期,只需在控制台中输入“date”。 - Anthony O.

12

这里有一个解决方案,既可以完成工作,又可以告知用户剩余时间。我几乎每天都使用它来在晚上运行脚本(使用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
}

9

您可以通过发送SIGSTOP信号来停止进程的执行,然后通过发送SIGCONT信号让它继续执行。

因此,您可以通过发送SIGSTOP信号来停止脚本的执行:

kill -SIGSTOP <pid>

然后使用at守护进程通过发送同样的SIGCONT信号来唤醒它。

想必你的脚本在休眠之前会告知at何时想要被唤醒。


8

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

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

再也不会因为输入错误的日期而迟到了,干杯!


如果精度很重要,有一个非常小的细节:如果在截止日期前不到1分钟,它总是会等待至少59秒。此外,这个脚本平均会有60次中的59次不能准时运行。 - Jan
顺便提一下,您可以轻松地将代码封装在一个函数中(无需更改代码),并像这样在shell脚本中调用它:till 7 45 - Jan

7

接着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
$

我认为你想要安排一个SIGCONT,而不是另一个SIGSTOP,所以信号或注释可能有误。否则,很高兴看到一个合适的例子。 - SpoonMeiser
糟糕,我打错了注释。现在已经修复了。但是你必须注意使用数字信号,因为它们可能不同。 - Dennis Williamson
我发现dash似乎不理解SIGCONT,所以起初我使用了-18,这是非便携的,然后我发现它喜欢CONT,所以我编辑了上面的答案来修复它。 - Dennis Williamson

4

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

好的解决方案,不过我认为$SECONDS是一个字符串,所以防御性代码应该是先获取子字符串,像这样SIGNCHAR=${SECONDS:0:1},然后再判断if [ "$SIGNCHAR" = "-" ]。这样就可以了。 - H2ONaCl

2

关注C time()和gettimeofday()之间的差异 - F. Hauri - Give Up GitHub

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接