bashスクリプトを特定の時間までスリープさせたい。だから、私は「sleep」のような、間隔をとらずに終了時間を取り、それまでスリープするコマンドが必要です。
「at」デーモンは解決策ではありません。特定の日時まで実行中のスクリプトをブロックする必要があるためです。
そのようなコマンドはありますか?
bashスクリプトを特定の時間までスリープさせたい。だから、私は「sleep」のような、間隔をとらずに終了時間を取り、それまでスリープするコマンドが必要です。
「at」デーモンは解決策ではありません。特定の日時まで実行中のスクリプトをブロックする必要があるためです。
そのようなコマンドはありますか?
回答:
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
、fromを使用する必要がありますbrew
→ これらの手順を参照してください
target_epoch=$(date -d 'tomorrow 03:00' +%s)
代わりに使用できます。
date
2つではなく1つのフォークを使用して同じことを行うことができます!私の答えの
この質問は4年前に行われたため、この最初の部分は古いbashバージョンに関するものです。
最終編集:2020年4月22日水曜日、10:30から10h:55の間(サンプルを読むために重要)
(注:この方法date -f
はPOSIXではなく、MacOSでは機能しません!Macの場合は、純粋にbash関数)
を減らすためにforks
、date
2回実行する代わりに、これを使用することを好みます。
sleep $(($(date -f - +%s- <<< $'tomorrow 21:30\nnow')0))
どこtomorrow 21:30
で認識した日付とフォーマットのいずれかの種類によって置き換えることができdate
、将来的に、。
ほぼ同じ:
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 およびその他の最新のシェルですが、以下を使用する必要があります。
sleep $(( ( $(printf 'tomorrow 21:30\nnow\n' | date -f - +%s-)0 )%86400 ))
MacOSでテスト済み!
私は1つの2つの小さな関数を書きました: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
最近 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
ユーザーによる使用を避けています!!(これは、1秒間に1,000行を含む非常に大きなログファイルで特定のイベントを生成および追跡するために作成しました)。
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
スリープ時間を計算するために変数ファイルに非常にすばやくアクセスします。
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
$EPOCHSECOND
して$EPOCHREALTIME
!との違いについての私の警告を読んでください$EPOCHSECOND
$EPOCHREALTIME
この関数は使用する$EPOCHREALTIME
ので$EPOCHSECOND
、次の秒を確立するために使用しないでください:
問題の例:次に2秒で丸めた時間を印刷しようとしています:
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
%86400
トリックは、2回が夏時間の移行にまたがらない場合にのみ機能します。
tzoff
私の答えで検索してください!
を使用します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
すべて完了!
-d
は、これまでの非POSIX拡張です。FreeBSDでは、夏時間のカーネルの値を設定しようとします。
これは、ジョブを実行し、残り時間についてユーザーに通知するソリューションです。私はほぼ毎日、夜間にスクリプトを実行するために使用しています(cron
Windowsで作業できなかったため、cygwinを使用しています)
&&
$ 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
}
プロセスにSIGSTOPシグナルを送信することにより、プロセスの実行を停止し、次にSIGCONTシグナルを送信することにより、プロセスの実行を再開させることができます。
したがって、SIGSTOPを送信してスクリプトを停止できます。
kill -SIGSTOP <pid>
次に、atデーモンを使用して、同じ方法でSIGCONTを送信してウェイクアップします。
おそらく、スクリプトは、スリープ状態になる前に、いつウェイクアップしたいかを通知します。
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
$
毎日同じパラメーターでスクリプトを実行できるように、時間と分のみをチェックするスクリプトが必要でした。明日はいつになるか心配したくない。そこで、別のアプローチを使用しました。
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はスクリプトの名前です)
仕事に遅れる日がなくなると、その日の入力ミスが発生します。乾杯!
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
SIGNCHAR=${SECONDS:0:1}
ありif [ "$SIGNCHAR" = "-" ]
ます。それはそれをする必要があります。
おそらく、「at」を使用して、そのシグナルを待っているスクリプトにシグナルを送信することができます。
私は実際にこの正確な目的のためにhttps://tamentis.com/projects/sleepuntil/を書きました。それは少しやり過ぎです-ほとんどのコードはBSD'at 'から来ているので、かなり標準に準拠しています:
$ sleepuntil noon && sendmail something
これは、複数のテストクライアントを同期するために今書いたものです。
#!/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
、ある端末と./sleep.py 10; ./test_client2.py
別の端末で実行することです。
until
特定の日時に設定されるように拡張するのは簡単です
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
主な答えを拡張するために、日付文字列の操作に関するいくつかの有効な例を次に示します。
sleep $(($(date -f - +%s- <<< $'+3 seconds\nnow')0)) && ls
sleep $(($(date -f - +%s- <<< $'3 seconds\nnow')0)) && ls
sleep $(($(date -f - +%s- <<< $'3 second\nnow')0)) && ls
sleep $(($(date -f - +%s- <<< $'+2 minute\nnow')0)) && ls
sleep $(($(date -f - +%s- <<< $'tomorrow\nnow')0)) && ls
sleep $(($(date -f - +%s- <<< $'tomorrow 21:30\nnow')0)) && ls
sleep $(($(date -f - +%s- <<< $'3 weeks\nnow')0)) && ls
sleep $(($(date -f - +%s- <<< $'3 week\nnow')0)) && ls
sleep $(($(date -f - +%s- <<< $'next Friday 09:00\nnow')0)) && ls
sleep $(($(date -f - +%s- <<< $'2027-01-01 00:00:01 UTC +5 hours\nnow')0)) && ls
上の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時間、さらには永遠に)、割り当てられた時間内にジョブを完了できることを確認します(たとえば、反復の1つだけが5分を少し超える場合、追いつく時間はありますが、次の1時間にラップアラウンドするもの)。
分指定には正確に2桁printf(1)
が必要なため、が必要ですdate
。
タリーを使用します。これは私が特にこれを行うために書いたツールです。
https://github.com/metaphyze/tarry/
これは、特定の時間まで待機するためのシンプルなコマンドラインツールです。これは、一定時間待機する「スリープ」と同じではありません。これは、特定の時間に何かを実行したい場合、またはサーバーが複数の非常に同時の要求を処理できるかどうかをテストするなど、まったく同時に複数のことを実行する可能性が高い場合に役立ちます。Linux、Mac、またはWindowsの「&&」で次のように使用できます。
tarry -until=16:03:04 && someOtherCommand
これは、午後4時3分4秒まで待機してからsomeOtherCommandを実行します。これは、すべて同時に開始するようにスケジュールされた複数のリクエストを実行する方法のLinux / Macの例です。
for request in 1 2 3 4 5 6 7 8 9 10
do
tarry -until=16:03:04 && date > results.$request &
done
Ubuntu、Linux、およびWindowsのバイナリは、ページ上のリンクから入手できます。