bash - second - sleep linux command
Bash: Dormir hasta una hora/fecha especĂfica (16)
Quiero que mi script bash duerma hasta un momento específico. Por lo tanto, quiero un comando como "dormir", que no requiere intervalo sino un tiempo 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 fecha / hora determinada.
¿Hay tal comando?
timeToWait = $ (($ end - $ start))
¡Cuidado con que "timeToWait" podría ser un número negativo! (por ejemplo, si especifica dormir hasta "15:57" y ahora es "15:58"). Por lo tanto, debe verificarlo 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
Aquí hay algo que escribí justo ahora para sincronizar múltiples 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 script duerme hasta el próximo tiempo "redondeado" o "agudo".
Un caso de uso simple es ejecutar ./sleep.py 10; ./test_client1.py
./sleep.py 10; ./test_client1.py
en un terminal y ./sleep.py 10; ./test_client2.py
./sleep.py 10; ./test_client2.py
en otro.
Aquí hay una solución que hace el trabajo Y le informa al usuario sobre cuánto tiempo le queda. Lo uso casi todos los días para ejecutar scripts durante la noche (usando cygwin, ya que no pude hacer que cron
funcione en Windows)
Caracteristicas
- Preciso hasta el segundo
- Detecta cambios en el tiempo del sistema y se adapta
- Salida inteligente que indica cuánto tiempo queda
- Formato de entrada de 24 horas
- devuelve verdadero para poder encadenar con
&&
Ejecución de muestra
$ 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
(La fecha al final no es parte de la función, pero debido a la && 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
}
Arreglé una pequeña utilidad llamada Hypnos para hacer esto. Se configura utilizando la sintaxis crontab y los bloques hasta ese momento.
#!/bin/bash
while [ 1 ]; do
hypnos "0 * * * *"
echo "running some tasks..."
# ...
done
Como lo menciona Outlaw Programmer, creo que la solución es solo dormir la cantidad correcta de segundos.
Para hacer esto en bash, haz 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 a 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 segundos, en su lugar necesitaría usar coreutils
de brew
→ vea estas instrucciones
Como se hizo esta pregunta hace 4 años, esta primera parte se refiere a las versiones anteriores de bash:
Método general
Para reducir las forks
, en lugar de ejecutar la date
dos veces, prefiero usar esto:
sleep $(($(date -f - +%s- <<< $''tomorrow 21:30/nnow'')0))
donde tomorrow 21:30
podría ser reemplazado por cualquier tipo de fecha y formato reconocidos por date
, en el futuro.
o mejor, para llegar a la siguiente HH:MM
significando hoy si es posible, mañana si es demasiado tarde:
sleep $((($(date -f - +%s- <<<$''21:30 tomorrow/nnow'')0)%86400))
Esto funciona bajo bash , ksh y otras conchas modernas, pero debes usar:
sleep $(( ( $(printf ''tomorrow 21:30/nnow/n'' | date -f - +%s-)0 )%86400 ))
bajo conchas más ligeras como ash o dash .
Nueva forma de bash (sin tenedor)
Como mi necesidad de este tipo de herramienta nunca ha superado las 24 horas, lo siguiente solo se referirá a la sintaxis de HH
, HH:MM
o HH:MM:SS
en las próximas 24 horas . (De todos modos, si necesita más, incluso podría volver al método anterior utilizando un tenedor hasta la date
. Intentar eliminar un tenedor en un script que se ejecuta durante muchos días es excesivo.)
Como las nuevas versiones de bash ofrecen una opción printf
para recuperar la fecha, para esta nueva forma de dormir hasta HH: MM sin usar date
u otro fork, he creado una pequeña función bash . Aquí está:
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
}
Entonces:
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
Tiempo de HiRes con bash bajo GNU / Linux
Bajo el reciente kernel de Linux, encontrará un archivo de variables llamado /proc/timer_list
donde podría leer un offset
y una variable now
, en nanosegundos . Entonces podemos calcular el tiempo de sueño para alcanzar el tiempo deseado más alto .
(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
y TIMER_LIST_SKIP
, la función accederá muy rápidamente al archivo variable /proc/timer_list
para calcular el tiempo de /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
Y finalmente
sleepUntilHires -q 15:11;date -f - +%F-%T.%N < <(echo now;sleep .97;echo now);sleepUntilHires 15:12;date +%F-%T.%N
2013-09-28-15:11:00.003973984
2013-09-28-15:11:00.974306364
sleep 59.024430416s, -> sam 28 sep 2013 15:12:00 CEST
2013-09-28-15:12:00.003447890
Donde, por 1 minuto completo , hay:
60sec - .97sec (reposo) - 59.024430416sec (reposo) = .005569584
Hay menos de 1/100 de segundo para hacer una horquilla hasta la date
, leer temporizadores y calcular el tiempo de reposo.
sleepUntilHires -q 16:16;date -f - +%F-%T.%N < <(echo now;sleep .97;echo now);sleepUntilHires 16:16:01;date +%F-%T.%N
2013-09-28-16:16:00.003657646
2013-09-28-16:16:00.974469831
sleep 0.024204370s, -> Sat Sep 28 16:16:01 2013
2013-09-28-16:16:01.003133202
De hecho, escribí https://tamentis.com/projects/sleepuntil/ para este propósito exacto. Es un poco exagerado: la mayoría del código proviene de BSD ''at'', por lo que es bastante compatible con las normas:
$ sleepuntil noon && sendmail something
En OpenBSD , lo siguiente podría usarse para compactar un trabajo */5
cinco minutos crontab(5)
en uno 00
cada hora (para asegurarse de que se generan menos correos electrónicos, todo mientras se realiza la misma tarea a 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 la date(1)
también rompería la sleep(1)
por diseño en la iteración final, ya que 60
minutos no es una hora válida (¡a menos que lo sea!), Por lo que no tendremos que esperar ningún tiempo adicional antes de obteniendo nuestro informe de correo electrónico.
También tenga en cuenta que si una de las iteraciones tarda más de 5 minutos asignados a ella, el sleep
fracasaría por diseño al no poder dormir (debido a que un número negativo se interpreta como una opción de línea de comando, en lugar de envolverlo). a la siguiente hora o incluso a la eternidad), asegurándose así de que su trabajo aún pueda completarse dentro de la hora asignada (por ejemplo, si solo una de las iteraciones tarda un poco más de 5 minutos, entonces todavía tendríamos el tiempo para ponernos al día, sin nada envolviendo la próxima hora).
El printf(1)
es necesario porque la date
espera exactamente dos dígitos para la especificación de minutos.
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`)
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
$
Puede calcular el número de segundos entre ahora y el tiempo de activación y usar el comando existente ''dormir''.
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 al enviar es un SIGSTOP:
kill -SIGSTOP <pid>
Y luego use at at deamon para reactivarlo enviándole un SIGCONT de la misma manera.
Presumiblemente, su script informará cuándo quiso ser despertado antes de ponerse a dormir.
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 sobre 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:
til 7 45 && mplayer song.ogg
(hasta que es el nombre del script)
no más días tarde en el trabajo porque te equivocaste en el día. ¡aclamaciones!
Tal vez podrías usar ''at'' para enviar una señal a tu script, que estaba esperando esa señal.
Use sleep
, pero calcule el tiempo usando date
. Querrá usar la date -d
para esto. Por ejemplo, supongamos que quieres esperar hasta la próxima semana:
expr `date -d "next week" +%s` - `date -d "now" +%s`
Solo sustituya "la próxima semana" con la fecha que desee esperar, luego asigne esta expresión a un valor y espere tantos segundos:
startTime=$(date +%s)
endTime=$(date -d "next week" +%s)
timeToWait=$(($endTime- $startTime))
sleep $timeToWait
¡Todo listo!
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