Saya ingin skrip bash saya tertidur sampai waktu tertentu. Jadi, saya ingin perintah seperti "tidur" yang tidak memerlukan jeda kecuali waktu berakhir dan tidur sampai saat itu.
The "at" -daemon bukanlah solusi, karena saya perlu memblokir skrip yang sedang berjalan hingga tanggal / waktu tertentu.
Apakah ada perintah seperti itu?
Jawaban:
Seperti yang disebutkan oleh Outlaw Programmer, menurut saya solusinya adalah tidur selama beberapa detik yang benar.
Untuk melakukan ini di bash, lakukan hal berikut:
current_epoch=$(date +%s) target_epoch=$(date -d '01/01/2010 12:00' +%s) sleep_seconds=$(( $target_epoch - $current_epoch )) sleep $sleep_seconds
Untuk menambahkan presisi hingga nanodetik (efektif lebih sekitar milidetik) gunakan misalnya sintaks ini:
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
Perhatikan bahwa macOS / OS X tidak mendukung presisi di bawah detik, Anda harus menggunakan
coreutils
frombrew
→ lihat instruksi inisumber
target_epoch=$(date -d 'tomorrow 03:00' +%s)
.date
bukan dua! Lihat bagian pertama dari jawaban sayaKarena pertanyaan ini diajukan 4 tahun yang lalu, bagian pertama ini menyangkut versi bash lama :
Edit terakhir: Rabu, 22 Apr 2020, antara pukul 10.30 dan 10.00: 55 (Penting untuk membaca sampel)
Metode umum (Hindari garpu yang tidak berguna!)
(Catatan: metode ini menggunakan
date -f
yang bukan POSIX dan tidak berfungsi di MacOS! Jika di bawah Mac, goto my purepestafungsi )Untuk mengurangi
forks
, daripada menjalankandate
dua kali, saya lebih suka menggunakan ini:Sampel awal yang sederhana
sleep $(($(date -f - +%s- <<< $'tomorrow 21:30\nnow')0))
di mana
tomorrow 21:30
dapat diganti dengan segala jenis tanggal dan format yang dikenalidate
, di masa mendatang.Dengan presisi tinggi (nanosec)
Hampir sama:
sleep $(bc <<<s$(date -f - +'t=%s.%N;' <<<$'07:00 tomorrow\nnow')'st-t')
Mencapai lain kali
Untuk mencapai makna selanjutnya
HH:MM
hari ini jika memungkinkan, besok jika terlambat:sleep $((($(date -f - +%s- <<<$'21:30 tomorrow\nnow')0)%86400))
Ini bekerja di bawah pesta, ksh dan cangkang modern lainnya, tetapi Anda harus menggunakan:
sleep $(( ( $(printf 'tomorrow 21:30\nnow\n' | date -f - +%s-)0 )%86400 ))
di bawah cangkang yang lebih ringan sepertiAbu atau berlari.
Murni pesta jalan, tidak ada garpu !!
Diuji di bawah MacOS!
Saya menulis
satudua fungsi kecil:sleepUntil
dansleepUntilHires
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)
Karena versi baru bash menawarkan
printf
opsi untuk mengambil tanggal, untuk cara baru tidur ini hingga HH: MM tanpa menggunakandate
atau garpu lain, saya telah membangun sedikitpestafungsi. Ini dia: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 }
Kemudian:
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
Jika target sebelum ini akan tidur sampai besok:
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
Waktu HiRes dengan pesta di bawah GNU / Linux
Baru pesta, dari versi 5.0 tambahkan
$EPOCHREALTIME
variabel baru dengan mikrodetik. Dari sini adasleepUntilHires
fungsi.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 }
Harap dicatat: penggunaan ini
read -t
yang sudah built-in, bukansleep
. Sayangnya, ini tidak akan berfungsi saat berjalan di latar belakang, tanpa TTY asli. Jangan ragu untuk menggantiread -t
dengansleep
jika Anda berencana untuk menjalankan ini di skrip latar belakang ... (Tetapi untuk proses latar belakang, pertimbangkan untuk menggunakancron
dan / atauat
alih-alih semua ini)Lewati paragraf berikutnya untuk tes dan peringatan tentang
$ËPOCHSECONDS
!Kernel terbaru menghindari penggunaan
/proc/timer_list
oleh pengguna !!Di bawah kernel Linux terbaru, Anda akan menemukan file variabel bernama `/ proc / timer_list` di mana Anda dapat membaca variabel` offset` dan `now`, dalam ** nanodetik **. Jadi, kami dapat menghitung waktu tidur untuk mencapai waktu * paling atas * yang diinginkan.(Saya menulis ini untuk menghasilkan dan melacak peristiwa tertentu pada file log yang sangat besar, berisi ribuan baris untuk satu detik).
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 }
Setelah menentukan dua variabel hanya-baca ,
TIMER_LIST_OFFSET
danTIMER_LIST_SKIP
, fungsi tersebut akan mengakses dengan sangat cepat file variabel/proc/timer_list
untuk menghitung waktu tidur:Fungsi tes kecil
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 }
Mungkin membuat sesuatu seperti:
Peduli untuk tidak mencampur
$EPOCHSECOND
dan$EPOCHREALTIME
!Baca peringatan saya tentang perbedaan antara
$EPOCHSECOND
dan$EPOCHREALTIME
Fungsi ini digunakan
$EPOCHREALTIME
jadi jangan gunakan$EPOCHSECOND
untuk menetapkan detik berikutnya :Contoh masalah: Mencoba mencetak waktu berikutnya dibulatkan oleh 2 detik:
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
Dapat menghasilkan:
sumber
%86400
trik ini hanya bekerja jika dua waktu tidak menjangkau transisi waktu musim panas.tzoff
di jawaban saya!Gunakan
sleep
, tapi hitung waktu menggunakandate
. Anda akan ingin menggunakandate -d
untuk ini. Misalnya, Anda ingin menunggu hingga minggu depan:expr `date -d "next week" +%s` - `date -d "now" +%s`
Cukup gantikan "minggu depan" dengan tanggal berapa pun yang ingin Anda tunggu, lalu tetapkan ekspresi ini ke nilai, dan tidur selama beberapa detik itu:
startTime=$(date +%s) endTime=$(date -d "next week" +%s) timeToWait=$(($endTime- $startTime)) sleep $timeToWait
Semua selesai!
sumber
-d
adalah ekstensi non-POSIX hingga saat ini. Di FreeBSD mencoba untuk mengatur nilai kernel untuk waktu musim panas.Berikut adalah solusi yang melakukan pekerjaan DAN memberi tahu pengguna tentang berapa banyak waktu yang tersisa. Saya menggunakannya hampir setiap hari untuk menjalankan skrip pada malam hari (menggunakan cygwin, karena saya tidak bisa
cron
bekerja di windows)fitur
&&
Contoh dijalankan
(Tanggal di akhir bukanlah bagian dari fungsi, tetapi karena
&& date
)Kode
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 }
sumber
Anda dapat menghentikan proses dari eksekusi, dengan mengirimkannya sinyal SIGSTOP, dan kemudian membuatnya melanjutkan eksekusi dengan mengirimkannya sinyal SIGCONT.
Jadi Anda bisa menghentikan skrip Anda dengan mengirimkan SIGSTOP:
kill -SIGSTOP <pid>
Dan kemudian gunakan at deamon untuk membangunkannya dengan mengirimkannya SIGCONT dengan cara yang sama.
Agaknya, skrip Anda akan memberi tahu kapan ia ingin dibangunkan sebelum tidur.
sumber
Di Ubuntu 12.04.4 LTS, berikut adalah input bash sederhana yang berfungsi:
sleep $(expr `date -d "03/21/2014 12:30" +%s` - `date +%s`)
sumber
Untuk mengikuti jawaban SpoonMeiser, berikut adalah contoh spesifiknya:
$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 $
sumber
Saya menginginkan skrip yang hanya memeriksa jam dan menit sehingga saya dapat menjalankan skrip dengan parameter yang sama setiap hari. Saya tidak ingin khawatir tentang hari apa besok. Jadi saya menggunakan pendekatan yang berbeda.
target="$1.$2" cur=$(date '+%H.%M') while test $target != $cur; do sleep 59 cur=$(date '+%H.%M') done
parameter pada skrip adalah jam dan menit, jadi saya bisa menulis sesuatu seperti:
(til adalah nama skrip)
tidak ada lagi hari terlambat di tempat kerja karena Anda salah mengetik hari itu. Bersulang!
sumber
Berhati-hatilah karena "timeToWait" bisa berupa angka negatif! (misalnya, jika Anda menetapkan untuk tidur sampai "15:57" dan sekarang "15:58"). Jadi Anda harus memeriksanya untuk menghindari kesalahan pesan aneh:
#!/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
sumber
SIGNCHAR=${SECONDS:0:1}
dan kemudianif [ "$SIGNCHAR" = "-" ]
. Itu harus dilakukan.Anda dapat menghitung jumlah detik antara sekarang dan waktu bangun dan menggunakan perintah 'tidur' yang ada.
sumber
Anda mungkin dapat menggunakan 'at' untuk mengirim sinyal ke skrip Anda, yang menunggu sinyal itu.
sumber
Saya sebenarnya menulis https://tamentis.com/projects/sleepuntil/ untuk tujuan yang tepat ini. Agak berlebihan, sebagian besar kode berasal dari BSD 'at' sehingga cukup memenuhi standar:
sumber
Inilah yang baru saja saya tulis untuk menyinkronkan beberapa klien uji:
#!/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)
Skrip ini tidur sampai waktu "bulat" atau "tajam" berikutnya.
Contoh penggunaan sederhana adalah menjalankan
./sleep.py 10; ./test_client1.py
di satu terminal dan./sleep.py 10; ./test_client2.py
di terminal lain.sumber
until
diatur ke tanggal / waktu tertentufunction 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
sumber
Saya mengumpulkan utilitas kecil yang disebut Hypnos untuk melakukan ini. Ini dikonfigurasi menggunakan sintaks crontab dan blok sampai saat itu.
#!/bin/bash while [ 1 ]; do hypnos "0 * * * *" echo "running some tasks..." # ... done
sumber
Untuk memperluas jawaban utama, berikut adalah beberapa contoh valid mengenai manipulasi string tanggal:
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
sumber
Di OpenBSD , berikut ini dapat digunakan untuk memadatkan pekerjaan
*/5
5 menitcrontab(5)
menjadi satu00
jam (untuk memastikan lebih sedikit email yang dibuat, semuanya sambil melakukan tugas yang sama dengan interval yang tepat):#!/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
Perhatikan bahwa
date(1)
itu juga akan merusaksleep(1)
by design pada iterasi terakhir, seperti60
menit bukanlah waktu yang valid (kecuali jika memang demikian!), Jadi kita tidak perlu menunggu waktu tambahan sebelum mendapatkan laporan email kita.Perhatikan juga bahwa jika salah satu iterasi membutuhkan waktu lebih dari 5 menit yang dialokasikan untuk itu, file
sleep
itu juga akan dengan anggun gagal dengan desain dengan tidak tidur sama sekali (karena apa angka negatif ditafsirkan sebagai opsi baris perintah, alih-alih membungkus sekitar ke satu jam ke depan atau bahkan keabadian), sehingga memastikan pekerjaan Anda masih dapat diselesaikan dalam waktu yang ditentukan (misalnya, jika hanya satu pengulangan yang membutuhkan waktu lebih dari 5 menit, maka kami masih memiliki waktu untuk menyusul, tanpa apa pun yang membungkus hingga satu jam berikutnya).Ini
printf(1)
diperlukan karenadate
mengharapkan tepat dua digit untuk spesifikasi menit.sumber
Gunakan tinggal. Ini adalah alat yang saya tulis khusus untuk melakukan ini.
https://github.com/metaphyze/tarry/
Ini adalah alat baris perintah sederhana untuk menunggu hingga waktu tertentu. Ini tidak sama dengan "tidur" yang menunggu dalam jangka waktu tertentu. Ini berguna jika Anda ingin menjalankan sesuatu pada waktu tertentu atau lebih mungkin mengeksekusi beberapa hal pada waktu yang sama persis seperti menguji apakah server dapat menangani beberapa permintaan yang sangat bersamaan. Anda dapat menggunakannya seperti ini dengan "&&" di Linux, Mac, atau Windows:
Ini akan menunggu sampai 4:03:04 dan kemudian menjalankan someOtherCommand. Berikut adalah contoh Linux / Mac tentang cara menjalankan beberapa permintaan yang semuanya dijadwalkan untuk dimulai pada waktu yang sama:
for request in 1 2 3 4 5 6 7 8 9 10 do tarry -until=16:03:04 && date > results.$request & done
Biner Ubuntu, Linux, dan Windows tersedia melalui tautan di halaman.
sumber