Chcę, aby mój skrypt bash spał do określonego czasu. Tak więc chcę polecenie typu „sen”, które nie zajmuje przerwy, ale kończy czas i śpi do tego czasu.
Demon „at” nie jest rozwiązaniem, ponieważ muszę blokować uruchomiony skrypt do określonej daty / godziny.
Czy jest takie polecenie?
Odpowiedzi:
Jak wspomniał Outlaw Programmer, myślę, że rozwiązaniem jest po prostu spanie przez odpowiednią liczbę sekund.
Aby to zrobić w bashu, wykonaj następujące czynności:
current_epoch=$(date +%s) target_epoch=$(date -d '01/01/2010 12:00' +%s) sleep_seconds=$(( $target_epoch - $current_epoch )) sleep $sleep_seconds
Aby dodać precyzję do nanosekund (efektywniej około milisekund), użyj np. Następującej składni:
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
Zauważ, że macOS / OS X nie obsługuje precyzji poniżej sekund, zamiast tego musisz użyć
coreutils
frombrew
→ zobacz te instrukcjeźródło
target_epoch=$(date -d 'tomorrow 03:00' +%s)
zamiast tego.date
zamiast dwóch! Zobacz pierwszą część mojej odpowiedziPonieważ to pytanie zadano 4 lata temu, ta pierwsza część dotyczy starych wersji basha:
Ostatnia edycja: środa, 22 kwietnia 2020 r., Między 10:30 a 10: 55 (ważne przy czytaniu próbek)
Metoda ogólna (unikaj bezużytecznych wideł!)
(Uwaga: ta metoda
date -f
nie jest POSIX i nie działa pod MacOS! Jeśli pod Mac, przejdź do mojego czystegogrzmotnąćfunkcja )Aby zmniejszyć
forks
, zamiast biegaćdate
dwa razy, wolę używać tego:Prosta próbka początkowa
sleep $(($(date -f - +%s- <<< $'tomorrow 21:30\nnow')0))
gdzie w przyszłości
tomorrow 21:30
może zostać zastąpiona dowolną datą i formatem rozpoznawanym przezdate
.Z wysoką precyzją (nanosec)
Prawie to samo:
sleep $(bc <<<s$(date -f - +'t=%s.%N;' <<<$'07:00 tomorrow\nnow')'st-t')
Sięgam następnym razem
Aby osiągnąć następny
HH:MM
sens dzisiaj, jeśli to możliwe, jutro, jeśli jest za późno:sleep $((($(date -f - +%s- <<<$'21:30 tomorrow\nnow')0)%86400))
To działa pod grzmotnąć, ksh i inne nowoczesne muszle, ale musisz użyć:
sleep $(( ( $(printf 'tomorrow 21:30\nnow\n' | date -f - +%s-)0 )%86400 ))
pod lżejszymi muszlami, jakpopiół lub dziarskość.
Czysty grzmotnąć sposób, bez widelca !!
Przetestowane pod MacOS!
Napisałem
jedendwa małe funkcje:sleepUntil
isleepUntilHires
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)
Ponieważ nowe wersje basha oferują
printf
opcję pobierania daty, dla tego nowego sposobu spania do GG: MM bez użyciadate
lub jakiegokolwiek innego widelca, zbudowałem trochęgrzmotnąćfunkcjonować. Oto ona: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 }
Następnie:
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
Jeśli cel jest wcześniej, będzie spał do jutra:
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
Czas HiRes z grzmotnąć pod GNU / Linux
Niedawny grzmotnąć, od wersji 5.0 dodaje nową
$EPOCHREALTIME
zmienną z mikrosekundami. Z tego wynikasleepUntilHires
funkcja.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 }
Uwaga: to użycie,
read -t
które jest wbudowane, zamiastsleep
. Niestety, to nie zadziała, gdy działa w tle, bez prawdziwego TTY. Zapraszam do zastąpieniaread -t
przezsleep
jeśli masz zamiar uruchomić to w skryptach tle ... (Ale za proces w tle, należy rozważyć użyciecron
i / lubat
zamiast to wszystko)Pomiń następny akapit, aby zobaczyć testy i ostrzeżenie
$ËPOCHSECONDS
!Najnowsze jądro unika używania
/proc/timer_list
przez użytkownika !!W ostatnim jądrze Linuksa znajdziesz plik zmiennych o nazwie `/ proc / timer_list`, w którym możesz odczytać` offset` i `now` zmienną, w ciągu ** nanosekund **. Możemy więc obliczyć czas snu, aby osiągnąć * najwyższy * pożądany czas.(Napisałem to, aby generować i śledzić określone zdarzenia w bardzo dużych plikach dziennika, zawierających tysiące wierszy przez jedną sekundę).
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 }
Po zdefiniowaniu dwóch zmiennych tylko do odczytu
TIMER_LIST_OFFSET
iTIMER_LIST_SKIP
, funkcja bardzo szybko uzyska dostęp do pliku zmiennej w/proc/timer_list
celu obliczenia czasu uśpienia:Mała funkcja testowa
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 }
Może renderować coś takiego:
Uważaj, aby nie mieszać
$EPOCHSECOND
i$EPOCHREALTIME
!Przeczytaj moje ostrzeżenie dotyczące różnicy między
$EPOCHSECOND
a$EPOCHREALTIME
Ta funkcja jest używana,
$EPOCHREALTIME
więc nie używaj$EPOCHSECOND
do ustalenia następnej sekundy :Przykładowy problem: Następna próba wydrukowania czasu zaokrąglonego o 2 sekundy:
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
Może produkować:
źródło
Użyj
sleep
, ale oblicz czas za pomocądate
. Będziesz chciał tego użyćdate -d
. Załóżmy na przykład, że chcesz poczekać do następnego tygodnia:expr `date -d "next week" +%s` - `date -d "now" +%s`
Po prostu zastąp „następny tydzień” dowolną datą, na którą chcesz czekać, a następnie przypisz to wyrażenie do wartości i śpij przez tyle sekund:
startTime=$(date +%s) endTime=$(date -d "next week" +%s) timeToWait=$(($endTime- $startTime)) sleep $timeToWait
Gotowe!
źródło
-d
jest jak dotąd rozszerzeniem spoza POSIX. We FreeBSD próbuje ustawić wartość jądra na czas letni.Oto rozwiązanie, które wykonuje zadanie ORAZ informuje użytkownika o pozostałym czasie. Używam go prawie codziennie do uruchamiania skryptów w nocy (używając cygwin, ponieważ nie mogłem dostać się
cron
do pracy na Windowsie)cechy
&&
Próbny przebieg
(Data na końcu nie jest częścią funkcji, ale ze względu na
&& date
)Kod
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 }
źródło
Możesz zatrzymać wykonywanie procesu, wysyłając mu sygnał SIGSTOP, a następnie wznowić wykonywanie, wysyłając mu sygnał SIGCONT.
Możesz więc zatrzymać skrypt, wysyłając SIGSTOP:
kill -SIGSTOP <pid>
A następnie użyj at demona, aby go obudzić, wysyłając SIGCONT w ten sam sposób.
Prawdopodobnie twój skrypt poinformuje o tym, kiedy chciał zostać obudzony przed zaśnięciem.
źródło
W Ubuntu 12.04.4 LTS tutaj jest proste wejście bash, które działa:
sleep $(expr `date -d "03/21/2014 12:30" +%s` - `date +%s`)
źródło
Aby kontynuować odpowiedź SpoonMeisera, oto konkretny przykład:
$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 $
źródło
Chciałem skryptu, który sprawdzałby tylko godziny i minuty, abym mógł codziennie uruchamiać skrypt z tymi samymi parametrami. Nie chcę się martwić, który dzień będzie jutro. Więc zastosowałem inne podejście.
target="$1.$2" cur=$(date '+%H.%M') while test $target != $cur; do sleep 59 cur=$(date '+%H.%M') done
parametrami skryptu są godziny i minuty, więc mogę napisać coś takiego:
(aż to nazwa skryptu)
nie ma więcej dni spóźnienia w pracy, ponieważ wpisałeś dzień. Twoje zdrowie!
źródło
Uważaj, że „timeToWait” może być liczbą ujemną! (na przykład, jeśli ustawisz tryb uśpienia do „15:57”, a teraz jest „15:58”). Musisz więc to sprawdzić, aby uniknąć dziwnych błędów wiadomości:
#!/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
źródło
SIGNCHAR=${SECONDS:0:1}
a potemif [ "$SIGNCHAR" = "-" ]
. Że należy to zrobić.Możesz obliczyć liczbę sekund między teraz a czasem budzenia i użyć istniejącego polecenia „uśpienia”.
źródło
Możesz użyć „at”, aby wysłać sygnał do twojego skryptu, który czekał na ten sygnał.
źródło
Właśnie w tym celu napisałem https://tamentis.com/projects/sleepuntil/ . To trochę przesadzone, większość kodu pochodzi z BSD `` at '', więc jest dość zgodny ze standardami:
źródło
Oto coś, co właśnie napisałem, aby zsynchronizować wielu klientów testowych:
#!/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)
Ten skrypt jest w stanie uśpienia do następnego „zaokrąglonego” lub „ostrego” czasu.
Prostym przypadkiem użycia jest uruchomienie
./sleep.py 10; ./test_client1.py
w jednym terminalu i./sleep.py 10; ./test_client2.py
innym.źródło
until
ustawić konkretną datę / godzinę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
źródło
W tym celu stworzyłem małe narzędzie o nazwie Hypnos . Do tego czasu jest konfigurowany przy użyciu składni crontab i bloków.
#!/bin/bash while [ 1 ]; do hypnos "0 * * * *" echo "running some tasks..." # ... done
źródło
Aby rozszerzyć główną odpowiedź, oto kilka ważnych przykładów dotyczących manipulacji ciągiem daty:
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
źródło
W OpenBSD , można użyć następującego rozwiązania, aby skompresować
*/5
5-minutowącrontab(5)
pracę do jednej00
godzinnej (aby mieć pewność, że generowanych jest mniej wiadomości e-mail, a wszystko to podczas wykonywania tego samego zadania w dokładnych odstępach czasu):#!/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
Zauważ, że złamałoby
date(1)
to równieżsleep(1)
projekt w ostatniej iteracji, ponieważ60
minuty nie są prawidłowym czasem (chyba że tak jest!), Więc nie będziemy musieli czekać dłużej przed otrzymaniem naszego raportu e-mail.Należy również pamiętać, że jeśli jedna z iteracji zajmie więcej niż 5 minut, plik
sleep
również łaskawie zawiodłaby z założenia, ponieważ w ogóle nie spałaby (z powodu liczby ujemnej interpretowanej jako opcja wiersza poleceń, zamiast zawijania do następnej godziny lub nawet wieczności), upewniając się w ten sposób, że Twoja praca może zostać ukończona w ciągu wyznaczonej godziny (np. jeśli tylko jedna z iteracji zajmie trochę więcej niż 5 minut, nadal będziemy mieli czas, aby nadrobić zaległości, bez wszystko, co zawija się do następnej godziny).printf(1)
Jest potrzebna, ponieważdate
oczekuje dokładnie dwie cyfry specyfikacji minut.źródło
Użyj smoły. To narzędzie, które napisałem, aby to zrobić.
https://github.com/metaphyze/tarry/
To proste narzędzie wiersza poleceń służące do czekania do określonej godziny. To nie to samo, co „sen”, który będzie czekał przez pewien czas. Jest to przydatne, jeśli chcesz wykonać coś w określonym czasie lub, co bardziej prawdopodobne, wykonać kilka rzeczy dokładnie w tym samym czasie, na przykład sprawdzić, czy serwer może obsłużyć wiele bardzo jednoczesnych żądań. Możesz go użyć w ten sposób z „&&” w systemie Linux, Mac lub Windows:
To czekałoby do 16:03:04, a następnie wykonywałoby niektóreOtherCommand. Oto przykład dla systemu Linux / Mac pokazujący, jak uruchomić wiele żądań zaplanowanych do jednoczesnego uruchomienia:
for request in 1 2 3 4 5 6 7 8 9 10 do tarry -until=16:03:04 && date > results.$request & done
Pliki binarne Ubuntu, Linux i Windows są dostępne za pośrednictwem łączy na stronie.
źródło