باش: النوم حتى وقت/تاريخ محدد
سؤال
أريد أن ينام نص Bash الخاص بي حتى وقت محدد. لذلك ، أريد أمرًا مثل "النوم" الذي لا يستغرق أي زمن ولكن في وقت انتهاء وينام حتى ذلك الحين.
"at" -daemon ليس حلاً ، حيث أحتاج إلى منع البرنامج النصي الجري حتى تاريخ/وقت معين.
هل هناك مثل هذا الأمر؟
المحلول
كما ذكر المبرمج Outlaw ، أعتقد أن الحل هو مجرد النوم للعدد الصحيح من الثواني.
للقيام بذلك في باش ، افعل ما يلي:
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
بدلا من ذلك → انظر هذه التعليمات
نصائح أخرى
يستخدم 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
كله تمام!
كما تم طرح هذا السؤال قبل 4 سنوات ، فإن هذا الجزء الأول يتعلق قديم إصدارات باش:
الطريقة العامة
لكى نقلل 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))
هذا يعمل تحت سحق, KSH وغيرها من الأصداف الحديثة ، ولكن عليك استخدام:
sleep $(( ( $(printf 'tomorrow 21:30\nnow\n' | date -f - +%s-)0 )%86400 ))
تحت ولاعة قذائف مثل رماد أو اندفاع.
جديد سحق الطريق (لا شوكة)
نظرًا لأن حاجتي لهذا النوع من الأدوات لم تنفجر أبدًا على مدار 24 ساعة ، فإن ما يلي سوف يهم فقط HH
, HH:MM
أو HH:MM:SS
معنى بناء الجملة في 24 ساعة القادمة. (على أي حال إذا كنت بحاجة إلى المزيد ، يمكنك حتى العودة إلى الطريقة القديمة باستخدام شوكة date
. محاولة القضاء على شوكة واحدة في نص يركض على مدار عدة أيام هو مبالغة.)
كما تقدم إصدارات جديدة من باش أ printf
خيار استرداد التاريخ ، لهذا الطريق الجديد إلى النوم حتى الساعة: مم: مم whithout باستخدام 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
يستأجر الوقت مع سحق تحت GNU/Linux
تحت kernel 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 Seccond ، ثم
- وقت الطباعة ، ثم
- حساب 0.07 ثانية اليسار ، إلى الثانية التالية
- النوم 0.07 ثانية ، ثم
- وقت الطباعة.
نوتا: .92 + 0.071 = .991
(على سطح المكتب)
يمكنك إيقاف عملية من التنفيذ ، من خلال إرسال إشارة sigstop ، ثم الحصول عليها لاستئناف التنفيذ عن طريق إرسال إشارة sigcont.
لذلك يمكنك إيقاف البرنامج النصي عن طريق الإرسال هو sigstop:
kill -SIGSTOP <pid>
ثم استخدم AT Deamon لإيقاظها عن طريق إرسالها 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
$
على Ubuntu 12.04.4 LTS إليك إدخال Bash البسيط الذي يعمل:
sleep $(expr `date -d "03/21/2014 12:30" +%s` - `date +%s`)
أردت برنامجًا نصيًا فحص الساعات والدقائق فقط حتى أتمكن من تشغيل البرنامج النصي بنفس المعلمات كل يوم. لا أريد أن أقلق بشأن أي يوم سيكون غدًا. لذلك استخدمت نهجًا مختلفًا.
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
(سمسم هو اسم البرنامج النصي)
لا مزيد من أيام في العمل لأنك أخطأت في اليوم. في صحتك!
فيما يلي حل يقوم بالمهمة ويبلغ المستخدم عن مقدار الوقت المتبقي. أستخدمه كل يوم تقريبًا لتشغيل البرامج النصية أثناء الليل (باستخدام Cygwin ، حيث لم أستطع الحصول عليها cron
للعمل على Windows)
سمات
- بدقة إلى الثانية
- يكتشف تغييرات وقت النظام والتكيف
- الإخراج الذكي يخبرنا مقدار الوقت المتبقي
- تنسيق الإدخال على مدار 24 ساعة
- يعود صحيح لتكون قادرا على التسلسل مع
&&
تشغيل العينة
$ 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
}
timetOwait = $ (($ end - $ start))
احذر أن "الجدول الزمني" يمكن أن يكون رقمًا سالبًا! (على سبيل المثال ، إذا حددت النوم حتى "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
يمكنك حساب عدد الثواني بين الآن ووقت الاستيقاظ واستخدام أمر "النوم" الحالي.
ربما يمكنك استخدام "في" لإرسال إشارة إلى البرنامج النصي الخاص بك ، والتي جلس في انتظار هذه الإشارة.
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
I put together a small utility called Hypnos to do this. It's configured using the crontab syntax and blocks until that time.
#!/bin/bash
while [ 1 ]; do
hypnos "0 * * * *"
echo "running some tasks..."
# ...
done
Here's something I wrote just now to synchronise multiple test clients:
#!/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)
This script sleeps until next "rounded" or "sharp" time.
A simple use case is to run ./sleep.py 10; ./test_client1.py
in one terminal and ./sleep.py 10; ./test_client2.py
in another.
On OpenBSD, the following could be used to compact a */5
5-minute crontab(5)
job into an 00
hourly one (to make sure fewer emails are generated, all whilst performing the same task at exact intervals):
#!/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
Note that the date(1)
would also break the sleep(1)
by design on the final iteration, as 60
minutes is not a valid time (unless it is!), thus we won't have to wait any extra time prior to getting our email report.
Also note that should one of the iterations take more than 5 minutes allotted to it, the sleep
would likewise graciously fail by design by not sleeping at all (due to what is a negative number interpreted as a command-line option, instead of wrapping around to the next hour or even eternity), thus making sure your job could still complete within the hour allotted (e.g., if only one of the iterations takes a little bit more than 5 minutes, then we would still have the time to catch up, without anything wrapping around to the next hour).
The printf(1)
is needed because date
expects exactly two digits for the minute specification.
I actually wrote https://tamentis.com/projects/sleepuntil/ for this exact purpose. It's a bit over-kill most of the code comes from BSD 'at' so it's fairly standard-compliant:
$ sleepuntil noon && sendmail something