Bash: спать до определенного времени / даты - PullRequest
72 голосов
/ 14 марта 2009

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

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

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

Ответы [ 16 ]

81 голосов
/ 14 марта 2009

Как упомянул 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 не поддерживает точность ниже секунд, вам нужно будет использовать coreutils из brew вместо & rarr; см. Эти инструкции

19 голосов
/ 14 марта 2009

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

Все готово!

13 голосов
/ 28 сентября 2013

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

Общий метод

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

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

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

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

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

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

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

под легче снаряды типа или .

Новый way (без форка)

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

Поскольку новые версии bash действительно предлагают опцию printf для получения даты, для этого нового способа сна до ЧЧ: ММ без использования date или любого другого форка Я создал небольшую функцию . Вот оно:

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

И наконец

Маленькая тестовая функция

tstSleepUntilHires () { 
    local now next last
    printf -v now "%(%s)T"
    printf -v next "%(%H:%M:%S)T" $((now+1))
    printf -v last "%(%H:%M:%S)T" $((now+2))
    sleepUntilHires $next
    date -f - +%F-%T.%N < <(echo now;sleep .92;echo now)
    sleepUntilHires $last
    date +%F-%T.%N
}

Может отображать что-то вроде:

sleep 0.155579469s, -> Mon Aug 20 20:42:51 2018
2018-08-20-20:42:51.005743047
2018-08-20-20:42:51.927112981
sleep 0.071764300s, -> Mon Aug 20 20:42:52 2018
2018-08-20-20:42:52.003165816
  • В начале следующей секунды,
  • время печати, затем
  • подождите 0,92 секунды, затем
  • время печати, затем
  • вычислить 0,07 секунды до следующей секунды
  • спать 0,07 секунды, затем
  • время печати. ​​

Примечание: .92 + 0.071 = .991 (на моем рабочем столе)

8 голосов
/ 14 марта 2009

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

Чтобы вы могли остановить свой скрипт, отправив SIGSTOP:

kill -SIGSTOP <pid>

А затем используйте at deamon, чтобы разбудить его, отправив ему SIGCONT таким же образом.

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

6 голосов
/ 20 марта 2014

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

sleep $(expr `date -d "03/21/2014 12:30" +%s` - `date +%s`)
6 голосов
/ 20 апреля 2009

Чтобы следовать ответу 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
$
4 голосов
/ 18 мая 2015

Вот решение, которое выполняет задание И информирует пользователя о том, сколько времени осталось. Я использую его почти каждый день для запуска сценариев в ночное время (используя 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
}
4 голосов
/ 19 января 2010

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

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

(пока имя сценария)

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

3 голосов
/ 05 мая 2009

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
1 голос
/ 20 февраля 2017

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

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