Bash: спящий режим до определенного времени/даты

Я хочу, чтобы мой bash script спал до определенного времени. Итак, мне нужна команда типа "sleep", которая не занимает интервала, а времени окончания и до тех пор остается.

"at" -daemon не является решением, так как мне нужно заблокировать запущенный script до определенной даты/времени.

Есть ли такая команда?

Ответ 1

Как упоминалось программистом Outlaw, я думаю, что решение - просто спать в течение правильного количества секунд.

Для этого в 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 не поддерживает точность ниже секунд, вам нужно будет использовать coreutils from brew вместо → см. эти инструкции

Ответ 2

Используйте 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

Все сделано!

Ответ 3

Вы можете остановить процесс от выполнения, отправив ему сигнал SIGSTOP, а затем заставить его возобновить выполнение, отправив ему сигнал SIGCONT.

Итак, вы можете остановить свой script, отправив SIGSTOP:

kill -SIGSTOP <pid>

И затем используйте функцию deamon, чтобы разбудить ее, отправив ей SIGCONT таким же образом.

Предположительно, ваш script будет информировать о том, когда он хочет проснуться, прежде чем начать спать.

Ответ 4

Как этот вопрос был задан 4 года назад, эта первая часть касается старой версии bash:

Общий метод

Чтобы уменьшить forks, вместо двухкратного запуска date, я предпочитаю использовать это:

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

где tomorrow 21:30 можно заменить любым видом даты и формата, распознанным date, в futur.

или лучше, для достижения следующего HH:MM означает сегодня, если возможно, завтра, если слишком поздно:

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

Эта работа под , и другие современные оболочки, но вы должны Применение:

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

если под более легкой оболочкой, например или .

Новый bash (нет вилки )

Поскольку моя потребность в таком инструменте никогда не выходила за 24 часа, следующее будет касаться только синтаксиса HH, HH:MM или HH:MM:SS в следующие 24 часа. (В любом случае, если вам нужно больше, вы даже можете вернуться к старому методу, используя fork для date. Попытка уничтожить одну вилку в script, запущенной в течение многих дней, является чрезмерной.)

Поскольку новая версия bash предлагает printf вариант для получения даты, для этого нового способа спящего режима до HH: MM без использования date или любой другой вилки, ve построить немного функция, там это является:

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

Время HiRes под GNU/Linux

В последнем ядре 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 для вычисления времени ожидания:

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

Где, в течение 1 минуты, есть:

60 сек -.97 сек (сон) - 59.024430416sec (сон) =.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

Ответ 5

Чтобы ответить на ответ 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
$

Ответ 6

Мне нужен script, который проверял только часы и минуты, чтобы я мог запускать script с одинаковыми параметрами каждый день. Я не хочу беспокоиться о том, какой день будет завтра. Поэтому я использовал другой подход.

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

параметры script - это часы и минуты, поэтому я могу написать что-то вроде:

til 7 45 && mplayer song.ogg

(til - имя script)

больше дней на работе, потому что вы не поняли день. ура!

Ответ 7

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 "[email protected]" >&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

Ответ 8

В Ubuntu 12.04.4 LTS представлен простой ввод bash, который работает:

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

Ответ 9

Вот решение, которое выполняет задание И информирует пользователя о том, сколько времени осталось. Я использую его почти каждый день, чтобы запускать сценарии в течение ночи (используя cygwin, поскольку я не мог получить cron для работы с окнами)

Функции

  • Точность до второго
  • Обнаруживает изменения системного времени и адаптирует
  • Интеллектуальный вывод, показывающий, сколько осталось времени.
  • 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
}

Ответ 10

Вы можете рассчитать количество секунд между временем пробуждения и использовать существующую команду "sleep".

Ответ 11

Возможно, вы можете использовать 'at' для отправки сигнала на ваш script, который сидел в ожидании этого сигнала.

Ответ 12

Я собрал небольшую утилиту Hypnos, чтобы сделать это. Он настраивается с использованием синтаксиса crontab и блоков до этого времени.

#!/bin/bash
while [ 1 ]; do
  hypnos "0 * * * *"
  echo "running some tasks..."
  # ...
done

Ответ 13

Вот что я написал сейчас, чтобы синхронизировать несколько тестовых клиентов:

#!/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)

Этот script спит до следующего "округленного" или "резкого" времени.

Простым вариантом является запуск ./sleep.py 10; ./test_client1.py в одном терминале и ./sleep.py 10; ./test_client2.py в другом.

Ответ 14

В OpenBSD для сжатия a */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 ожидает ровно две цифры для спецификации минут.

Ответ 15

 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

Ответ 16

Я действительно написал https://tamentis.com/projects/sleepuntil/ для этой цели. Это немного более-убивает большую часть кода, исходя из BSD 'at', поэтому он довольно стандартно-совместимый:

$ sleepuntil noon && sendmail something