Quiero que mi script de bash duerma hasta una hora específica. Entonces, quiero un comando como "dormir" que no toma un intervalo sino una hora de finalización y duerme hasta entonces.
El "at" -daemon no es una solución, ya que necesito bloquear un script en ejecución hasta una determinada fecha / hora.
¿Existe tal comando?
Respuestas:
Como lo mencionó Outlaw Programmer, creo que la solución es simplemente dormir el número correcto de segundos.
Para hacer esto en bash, haga lo siguiente:
current_epoch=$(date +%s) target_epoch=$(date -d '01/01/2010 12:00' +%s) sleep_seconds=$(( $target_epoch - $current_epoch )) sleep $sleep_seconds
Para agregar precisión hasta nanosegundos (efectivamente más alrededor de milisegundos) use, por ejemplo, esta sintaxis:
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
Tenga en cuenta que macOS / OS X no admite precisión por debajo de los segundos, necesitaría usar
coreutils
de en subrew
lugar → consulte estas instruccionesfuente
target_epoch=$(date -d 'tomorrow 03:00' +%s)
en su lugar.date
lugar de dos! Vea la primera parte de mi respuestaComo esta pregunta se hizo hace 4 años, esta primera parte se refiere a versiones antiguas de bash:
Última edición: miércoles 22 de abril de 2020, algo entre las 10:30 y las 10h: 55 (importante para las muestras de lectura)
Método general (¡Evite los tenedores inútiles!)
(Nota: este uso del método
date -f
! Wich hay POSIX y no hacer el trabajo bajo MacOS Si en Mac, ir a mi puraintentofunción )Para reducir
forks
, en lugar de ejecutardate
dos veces, prefiero usar esto:Muestra inicial simple
sleep $(($(date -f - +%s- <<< $'tomorrow 21:30\nnow')0))
donde
tomorrow 21:30
podría ser reemplazado por cualquier tipo de fecha y formato reconocido pordate
, en el futuro.Con alta precisión (nanosec)
Casi lo mismo:
sleep $(bc <<<s$(date -f - +'t=%s.%N;' <<<$'07:00 tomorrow\nnow')'st-t')
Alcanzando la próxima vez
Para alcanzar el siguiente
HH:MM
significado hoy si es posible, mañana si es demasiado tarde:sleep $((($(date -f - +%s- <<<$'21:30 tomorrow\nnow')0)%86400))
Esto funciona bajo intento, ksh y otras carcasas modernas, pero debes usar:
sleep $(( ( $(printf 'tomorrow 21:30\nnow\n' | date -f - +%s-)0 )%86400 ))
bajo conchas más ligeras comoceniza o guión.
Puro intento camino, sin tenedor !!
¡Probado en MacOS!
Escribí
unode dos pequeñas funciones:sleepUntil
ysleepUntilHires
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)
Como las nuevas versiones de bash ofrecen una
printf
opción para recuperar la fecha, para esta nueva forma de dormir hasta HH: MM sin usardate
o cualquier otra bifurcación, he construido un pocointentofunción. Aquí está: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 }
Entonces:
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
Si el objetivo es antes, este dormirá hasta mañana:
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
Hola tiempo con intento bajo GNU / Linux
Reciente intento, a partir de la versión 5.0 agregue una nueva
$EPOCHREALTIME
variable con microsegundos. De esto hay unasleepUntilHires
función.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 }
Tenga en cuenta: este uso
read -t
está integrado, en lugar desleep
. Desafortunadamente, esto no funcionará cuando se ejecute en segundo plano, sin TTY real. Siéntase libre de reemplazarread -t
porsleep
si planea ejecutar esto en scripts en segundo plano ... (Pero para el proceso en segundo plano, considere usarcron
y / o enat
lugar de todo esto)Omita el siguiente párrafo para las pruebas y advertencias
$ËPOCHSECONDS
.¡El kernel reciente evita el uso
/proc/timer_list
por parte del usuario!Bajo el reciente kernel de Linux, encontrará un archivo de variables llamado `/ proc / timer_list` donde puede leer un` offset` y una variable `now`, en ** nanosegundos **. Por lo tanto, podemos calcular el tiempo de sueño para alcanzar el * máximo * tiempo deseado.(Escribí esto para generar y rastrear eventos específicos en archivos de registro muy grandes, que contienen mil líneas por un segundo).
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 }
Después de definir dos variables de solo lectura ,
TIMER_LIST_OFFSET
yTIMER_LIST_SKIP
, la función accederá muy rápidamente al archivo de variables/proc/timer_list
para calcular el tiempo de suspensión:Pequeña función de prueba
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 }
Puede representar algo como:
¡Cuidado de no mezclar
$EPOCHSECOND
y$EPOCHREALTIME
!Lea mi advertencia sobre la diferencia entre
$EPOCHSECOND
y$EPOCHREALTIME
Esta función se usa,
$EPOCHREALTIME
así que no la use$EPOCHSECOND
para establecer el siguiente segundo :Problema de muestra: Intentando imprimir el tiempo siguiente redondeado en 2 segundos:
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
Puede producir:
fuente
Use
sleep
, pero calcule el tiempo usandodate
. Querrás usarlodate -d
para esto. Por ejemplo, digamos que desea esperar hasta la próxima semana:expr `date -d "next week" +%s` - `date -d "now" +%s`
Simplemente sustituya "la próxima semana" con la fecha que le gustaría esperar, luego asigne esta expresión a un valor y duerma durante tantos segundos:
startTime=$(date +%s) endTime=$(date -d "next week" +%s) timeToWait=$(($endTime- $startTime)) sleep $timeToWait
¡Todo listo!
fuente
-d
es una extensión que no es POSIX hasta la fecha. En FreeBSD, intenta establecer el valor del kernel para el horario de verano.Aquí hay una solución que hace el trabajo E informa al usuario sobre cuánto tiempo queda. Lo uso casi todos los días para ejecutar scripts durante la noche (usando cygwin, ya que no pude
cron
trabajar en Windows)Caracteristicas
&&
Ejecución de muestra
(La fecha al final no es parte de la función, sino debido a
&& date
)Código
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 }
fuente
Puede detener la ejecución de un proceso enviándole una señal SIGSTOP y luego hacer que reanude la ejecución enviándole una señal SIGCONT.
Para que pueda detener su script enviando un SIGSTOP:
kill -SIGSTOP <pid>
Y luego usa el at deamon para despertarlo enviándole un SIGCONT de la misma manera.
Es de suponer que su guión le informará cuándo desea que lo despierten antes de ponerse a dormir.
fuente
En Ubuntu 12.04.4 LTS, aquí está la entrada bash simple que funciona:
sleep $(expr `date -d "03/21/2014 12:30" +%s` - `date +%s`)
fuente
Para seguir la respuesta de SpoonMeiser, aquí hay un ejemplo específico:
$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 $
fuente
Quería un script que solo verificara las horas y los minutos para poder ejecutar el script con los mismos parámetros todos los días. No quiero preocuparme de qué día será mañana. Entonces usé un enfoque diferente.
target="$1.$2" cur=$(date '+%H.%M') while test $target != $cur; do sleep 59 cur=$(date '+%H.%M') done
los parámetros del script son las horas y los minutos, así que puedo escribir algo como:
(hasta que es el nombre del guión)
no más días tarde en el trabajo porque escribiste mal el día. ¡salud!
fuente
¡Tenga en cuenta que "timeToWait" podría ser un número negativo! (por ejemplo, si especifica dormir hasta las "15:57" y ahora son las "15:58"). Así que tienes que comprobarlo para evitar errores de mensajes extraños:
#!/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
fuente
SIGNCHAR=${SECONDS:0:1}
y luegoif [ "$SIGNCHAR" = "-" ]
. Deberias hacer eso.Puede calcular el número de segundos entre ahora y la hora del despertador y utilizar el comando "dormir" existente.
fuente
Quizás podría usar 'at' para enviar una señal a su guión, que estaba esperando esa señal.
fuente
De hecho, escribí https://tamentis.com/projects/sleepuntil/ para este propósito exacto. Es un poco exagerado, la mayor parte del código proviene de BSD 'en', por lo que es bastante compatible con los estándares:
fuente
Aquí hay algo que escribí hace un momento para sincronizar varios clientes de prueba:
#!/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)
Este guión permanece inactivo hasta el próximo momento "redondeado" o "puntual".
Un caso de uso simple es ejecutar
./sleep.py 10; ./test_client1.py
en una terminal y./sleep.py 10; ./test_client2.py
en otra.fuente
until
se establezca en una fecha / hora específicafunction 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
fuente
Reuní una pequeña utilidad llamada Hypnos para hacer esto. Se configura usando la sintaxis crontab y se bloquea hasta ese momento.
#!/bin/bash while [ 1 ]; do hypnos "0 * * * *" echo "running some tasks..." # ... done
fuente
Para extender la respuesta principal, aquí hay algunos ejemplos válidos con respecto a la manipulación de la cadena de fecha:
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
fuente
En OpenBSD , lo siguiente podría usarse para compactar un trabajo de
*/5
5 minutoscrontab(5)
en00
uno por hora (para asegurarse de que se generen menos correos electrónicos, todo mientras se realiza la misma tarea en intervalos exactos):#!/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
Tenga en cuenta que
date(1)
también rompería elsleep(1)
diseño en la iteración final, ya que los60
minutos no son un tiempo válido (¡a menos que lo sea!), Por lo que no tendremos que esperar más tiempo antes de recibir nuestro informe por correo electrónico.También tenga en cuenta que si una de las iteraciones toma más de 5 minutos asignados, el
sleep
diseño también fallaría gentilmente al no dormir en absoluto (debido a lo que es un número negativo interpretado como una opción de línea de comandos, en lugar de ajustarse a la próxima hora o incluso la eternidad), asegurándonos así de que su trabajo podría completarse dentro de la hora asignada (por ejemplo, si solo una de las iteraciones toma un poco más de 5 minutos, entonces aún tendríamos tiempo para ponernos al día, sin cualquier cosa que envuelva a la próxima hora).Se
printf(1)
necesita porquedate
espera exactamente dos dígitos para la especificación de minutos.fuente
Utilice alquitrán. Es una herramienta que escribí para hacer esto específicamente.
https://github.com/metaphyze/tarry/
Esta es una herramienta de línea de comandos simple para esperar hasta una hora específica. Esto no es lo mismo que "dormir", que esperará durante un tiempo. Esto es útil si desea ejecutar algo en un momento específico o, más probablemente, ejecutar varias cosas exactamente al mismo tiempo, como probar si un servidor puede manejar múltiples solicitudes muy simultáneas. Puede usarlo así con "&&" en Linux, Mac o Windows:
Esto esperaría hasta las 4:03:04 PM y luego ejecutaría someOtherCommand. Aquí hay un ejemplo de Linux / Mac de cómo ejecutar varias solicitudes, todas programadas para iniciarse al mismo tiempo:
for request in 1 2 3 4 5 6 7 8 9 10 do tarry -until=16:03:04 && date > results.$request & done
Los binarios de Ubuntu, Linux y Windows están disponibles a través de enlaces en la página.
fuente