web-dev-qa-db-ja.com

Bash:特定の時刻/日付までスリープ

特定の時間までbashスクリプトをスリープさせたい。したがって、終了時間以外の間隔をとらずにそれまでスリープする「スリープ」のようなコマンドが必要です。

「at」デーモンは、特定の日時まで実行中のスクリプトをブロックする必要があるため、解決策ではありません。

そのようなコマンドはありますか?

72
theomega

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を使用する必要があります→ これらの指示を参照

81
SpoonMeiser

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

出来た!

19
John Feminella

この質問は4年前に尋ねられたので、この最初の部分はold bashバージョンに関するものです:

一般的な方法

forksを2回実行する代わりに、dateを減らすために、これを使用することを好みます。

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

ここで、tomorrow 21:30は、将来dateで認識されるあらゆる種類の日付と形式に置き換えることができます。

以上の場合、next HH:MMに到達すると、可能な場合は今日、遅すぎる場合は明日を意味します。

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

これは bashksh およびその他の最新のシェルの下で機能しますが、次を使用する必要があります。

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

ライターの下で ash または dash のようなシェル。

新しい bash way(フォークなし)

この種のツールの必要性は24時間を超えたことがないため、以下はHHHH:MM、またはHH:MM:SS構文の意味のみに関係します次の24時間 。 (とにかくもっと必要な場合は、dateへのフォークを使用して古いメソッドに戻ることもできます。何日も実行されているスクリプトで1つのフォークを削除しようとするのはやり過ぎです。)

Bashの新しいバージョンでは、日付を取得するprintfオプションが提供されるため、この新しい方法HH:MMまでスリープするdateまたはその他のフォークを使用せずに、少し bash 関数を作成しました。ここにあります:

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

GNU/Linuxで bash を使用したHiRes時間

最近のLinuxカーネルでは、/proc/timer_listという名前の変数ファイルがあり、offsetおよびnow変数をnanoseconds。したがって、スリープ時間を計算してvery top希望の時間に到達することができます。

(1秒間に数千行を含む非常に大きなログファイルで特定のイベントを生成および追跡するためにこれを作成しました)。

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
}

2つの読み取り専用変数、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(デスクトップ上)

13
F. Hauri

SIGSTOPシグナルを送信してプロセスの実行を停止し、SIGCONTシグナルを送信して実行を再開できます。

したがって、SIGSTOPを送信することでスクリプトを停止できます。

kill -SIGSTOP <pid>

次に、at deamonを使用して、同じ方法でSIGCONTを送信して起動します。

おそらく、スクリプトは、自身をスリープ状態にする前に、いつ目覚めさせたいかを通知します。

8
SpoonMeiser

Ubuntu 12.04.4 LTSでは、次の単純なbash入力が機能します。

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

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

毎日同じパラメーターでスクリプトを実行できるように、時間と分のみをチェックするスクリプトが必要でした。私は明日がどの日かを心配したくありません。そこで、私は別のアプローチを使用しました。

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はスクリプトの名前です)

仕事に遅れる日はもうありません。乾杯!

4
kolomer

これは、ジョブを実行し、残り時間をユーザーに通知するソリューションです。私は夜中にスクリプトを実行するためにほぼ毎日それを使用します(ウィンドウでcronを動作させることができなかったため、cygwinを使用します)

特徴

  • 秒まで正確に
  • システム時刻の変更を検出して適応します
  • 残り時間を示すインテリジェント出力
  • 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
Camusensei

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
3
anonymous

現在から起床時間までの秒数を計算し、既存の「sleep」コマンドを使用できます。

1

おそらく 'at'を使用して、スクリプトにシグナルを送信し、そのシグナルを待っていました。

1
Kim Reece
 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
1

これを行うために Hypnos という小さなユーティリティをまとめました。 crontab構文を使用して構成され、それまでブロックされます。

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

OpenBSDでは、次を使用して*/5 5分間 crontab(5)を圧縮できます。00の1時間ごとのジョブ(正確な間隔で同じタスクを実行しながら、生成される電子メールの数を減らすため):

#!/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分は有効な時間ではないため(そうでない場合!)、メールレポートを取得する前に余分な時間を待つ必要はありません。

また、反復の1つに5分以上が割り当てられている場合、sleepも同様に設計上、まったくスリープしないことで優雅に失敗します(負の数はコマンドラインオプションとして解釈されるため) 、次の時間または永遠に折り返すのではなく)、割り当てられた時間内にジョブが完了することを確認します(たとえば、反復の1つだけが5分以上かかる場合は、次の1時間まで何もラップすることなく、追いつく時間です。

printf(1) が必要なのは、dateが分指定に正確に2桁を想定しているためです。

0
cnst

これは、複数のテストクライアントを同期するために今書いたものです。

#!/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 1つの端末で./sleep.py 10; ./test_client2.py別の。

0
Dima Tisnek

この正確な目的のために、実際に https://tamentis.com/projects/sleepuntil/ を書きました。ほとんどのコードはBSDの「at」に由来するため、少し過剰になりすぎているため、かなり標準に準拠しています。

$ sleepuntil noon && sendmail something
0
Bertrand Janin