Quero que meu script bash durma até um horário específico. Então, eu quero um comando como "sleep" que não leva nenhum intervalo, mas um tempo de término e dorme até então.
O daemon "at" não é uma solução, pois preciso bloquear um script em execução até uma determinada data / hora.
Existe tal comando?
Respostas:
Conforme mencionado pelo Outlaw Programmer, acho que a solução é apenas dormir pelo número correto de segundos.
Para fazer isso no bash, faça o seguinte:
current_epoch=$(date +%s) target_epoch=$(date -d '01/01/2010 12:00' +%s) sleep_seconds=$(( $target_epoch - $current_epoch )) sleep $sleep_seconds
Para adicionar precisão até nanossegundos (efetivamente mais em torno de milissegundos), use, por exemplo, esta sintaxe:
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
Observe que o macOS / OS X não oferece suporte à precisão abaixo de segundos, você precisaria usar a
coreutils
partir debrew
→ consulte estas instruçõesfonte
target_epoch=$(date -d 'tomorrow 03:00' +%s)
lugar.date
vez de dois! Veja a 1ª parte da minha respostaComo essa pergunta foi feita há 4 anos, a primeira parte se refere a versões antigas do bash:
Última edição: Quarta-feira, 22 de abril de 2020, algo entre 10h30 e 10h55 (importante para ler amostras)
Método geral (evite garfos inúteis!)
(Nota: este método não usa
date -f
POSIX e não funciona no MacOS! Se for no Mac, vá para o meu purobashfunção )Para reduzir
forks
, em vez de correrdate
duas vezes, prefiro usar isto:Amostra inicial simples
sleep $(($(date -f - +%s- <<< $'tomorrow 21:30\nnow')0))
onde
tomorrow 21:30
poderia ser substituído por qualquer tipo de data e formato reconhecido pordate
, no futuro.Com alta precisão (nanosec)
Quase o mesmo:
sleep $(bc <<<s$(date -f - +'t=%s.%N;' <<<$'07:00 tomorrow\nnow')'st-t')
Alcançando a próxima vez
Para alcançar o próximo
HH:MM
significado hoje, se possível, amanhã se for tarde demais:sleep $((($(date -f - +%s- <<<$'21:30 tomorrow\nnow')0)%86400))
Isso funciona sob bash, ksh e outros shells modernos, mas você tem que usar:
sleep $(( ( $(printf 'tomorrow 21:30\nnow\n' | date -f - +%s-)0 )%86400 ))
sob conchas mais leves comocinza ou traço.
Puro bash caminho, sem garfo !!
Testado em MacOS!
Eu escrevi
umduas funções pequenos:sleepUntil
esleepUntilHires
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 as novas versões do bash oferecem uma
printf
opção para recuperar a data, para esta nova maneira de dormir até HH: MM sem usardate
ou qualquer outro fork, eu construí um poucobashfunção. Aqui 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 }
Então:
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
Se o alvo estiver antes, isso dormirá até amanhã:
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
Tempo de HiRes com bash sob GNU / Linux
Recente bash, da versão 5.0 adiciona uma nova
$EPOCHREALTIME
variável com microssegundos. Disto existe umasleepUntilHires
função.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 }
Atenção: este uso
read -t
é embutido, em vez desleep
. Infelizmente, isso não funcionará quando executado em segundo plano, sem TTY real. Sinta-se à vontade para substituirread -t
porsleep
se você planeja executar isso em scripts de segundo plano ... (mas para o processo de segundo plano, considere usarcron
e / ou emat
vez de tudo isso)Pule o próximo parágrafo para testes e avisos sobre
$ËPOCHSECONDS
!O kernel recente evita o uso
/proc/timer_list
pelo usuário !!No kernel Linux recente, você encontrará um arquivo de variáveis chamado `/ proc / timer_list` onde você pode ler um` offset` e uma variável `now`, em ** nanossegundos **. Portanto, podemos calcular o tempo de sono para chegar ao * máximo * tempo desejado.(Eu escrevi isso para gerar e rastrear eventos específicos em arquivos de log muito grandes, contendo mil linhas por um 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 }
Depois de definir duas variáveis somente leitura ,
TIMER_LIST_OFFSET
eTIMER_LIST_SKIP
, a função acessará muito rapidamente o arquivo de variável/proc/timer_list
para calcular o tempo de sono:Pequena função de teste
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 }
Pode renderizar algo como:
Cuidado para não misturar
$EPOCHSECOND
e$EPOCHREALTIME
!Leia meu aviso sobre a diferença entre
$EPOCHSECOND
e$EPOCHREALTIME
Esta função usa,
$EPOCHREALTIME
portanto, não use$EPOCHSECOND
para estabelecer o próximo segundo :Problema de amostra: tentar imprimir a próxima hora arredondada em 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
Pode produzir:
fonte
%86400
truque só funciona se os dois tempos não abrangem uma transição do horário de verão.tzoff
na minha resposta!Use
sleep
, mas calcule o tempo usandodate
. Você vai querer usardate -d
para isso. Por exemplo, digamos que você queira esperar até a próxima semana:expr `date -d "next week" +%s` - `date -d "now" +%s`
Basta substituir "próxima semana" pela data que você gostaria de esperar, atribuir um valor a essa expressão e dormir por alguns segundos:
startTime=$(date +%s) endTime=$(date -d "next week" +%s) timeToWait=$(($endTime- $startTime)) sleep $timeToWait
Tudo feito!
fonte
-d
é uma extensão não POSIX até o momento. No FreeBSD, ele tenta definir o valor do kernel para o horário de verão.Aqui está uma solução que faz o trabalho E informa ao usuário quanto tempo resta. Eu o uso quase todos os dias para executar scripts durante a noite (usando o cygwin, pois não consegui
cron
trabalhar no Windows)Características
&&
Execução de amostra
(A data no final não faz parte da função, mas devido ao
&& 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 }
fonte
Você pode interromper a execução de um processo enviando um sinal SIGSTOP e, em seguida, fazer com que ele retome a execução enviando um sinal SIGCONT.
Portanto, você pode interromper o seu script enviando um SIGSTOP:
kill -SIGSTOP <pid>
E então use o at deamon para acordá-lo enviando um SIGCONT da mesma maneira.
Provavelmente, seu script o informará de quando quis ser acordado antes de adormecer.
fonte
No Ubuntu 12.04.4 LTS, aqui está a entrada bash simples que funciona:
sleep $(expr `date -d "03/21/2014 12:30" +%s` - `date +%s`)
fonte
Para seguir a resposta de SpoonMeiser, aqui está um exemplo 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 $
fonte
Eu queria um script que verificasse apenas as horas e minutos para que eu pudesse executar o script com os mesmos parâmetros todos os dias. Não quero me preocupar em que dia será amanhã. Então, usei uma abordagem diferente.
target="$1.$2" cur=$(date '+%H.%M') while test $target != $cur; do sleep 59 cur=$(date '+%H.%M') done
os parâmetros do script são horas e minutos, então posso escrever algo como:
(til é o nome do script)
não há mais dias de atraso no trabalho porque você digitou incorretamente o dia. Felicidades!
fonte
Esteja ciente de que "timeToWait" pode ser um número negativo! (por exemplo, se você especificar dormir até "15:57" e agora é "15:58"). Portanto, você deve verificá-lo para evitar erros de mensagem estranhos:
#!/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
fonte
SIGNCHAR=${SECONDS:0:1}
e depoisif [ "$SIGNCHAR" = "-" ]
. Isso deve resolver.Você pode calcular o número de segundos entre agora e a hora de despertar e usar o comando 'sleep' existente.
fonte
Você poderia talvez usar 'at' para enviar um sinal para seu script, que estava esperando por esse sinal.
fonte
Na verdade, escrevi https://tamentis.com/projects/sleepuntil/ para esse propósito exato. É um pouco exagerado, a maioria do código vem do BSD 'às', então é bastante compatível com o padrão:
fonte
Aqui está algo que escrevi agora para sincronizar vários clientes de teste:
#!/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 dorme até o próximo tempo "arredondado" ou "agudo".
Um caso de uso simples é executar
./sleep.py 10; ./test_client1.py
em um terminal e./sleep.py 10; ./test_client2.py
em outro.fonte
until
seja definido para uma data / 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
fonte
Eu criei um pequeno utilitário chamado Hypnos para fazer isso. É configurado usando a sintaxe do crontab e blocos até aquele momento.
#!/bin/bash while [ 1 ]; do hypnos "0 * * * *" echo "running some tasks..." # ... done
fonte
Para estender a resposta principal, aqui estão alguns exemplos válidos sobre a manipulação de string de data:
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
fonte
No OpenBSD , o seguinte pode ser usado para compactar um trabalho de
*/5
5 minutoscrontab(5)
em um de00
hora em hora (para garantir que menos e-mails sejam gerados, tudo enquanto executa a mesma tarefa em intervalos exatos):#!/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
Observe que o
date(1)
também interromperia osleep(1)
by design na iteração final, pois60
minutos não é um tempo válido (a menos que seja!), Portanto, não teremos que esperar nenhum tempo extra antes de receber nosso relatório por email.Observe também que, se uma das iterações levar mais de 5 minutos atribuídos a ela, o
sleep
também falhará graciosamente por design por não dormir (devido ao que é um número negativo interpretado como uma opção de linha de comando, em vez de envolver a próxima hora ou mesmo a eternidade), garantindo assim que seu trabalho ainda possa ser concluído dentro da hora alocada (por exemplo, se apenas uma das iterações levar um pouco mais de 5 minutos, então ainda teríamos tempo para recuperar o atraso, sem qualquer coisa envolvendo até a próxima hora).O
printf(1)
é necessário porquedate
espera exatamente dois dígitos para a especificação de minuto.fonte
Use tarry. É uma ferramenta que escrevi especificamente para fazer isso.
https://github.com/metaphyze/tarry/
Esta é uma ferramenta de linha de comando simples para esperar até um horário específico. Isso não é o mesmo que "dormir", que vai esperar um certo tempo. Isso é útil se você deseja executar algo em um momento específico ou, mais provavelmente, executar várias coisas exatamente ao mesmo tempo, como testar se um servidor pode lidar com várias solicitações simultâneas. Você pode usá-lo assim com "&&" no Linux, Mac ou Windows:
Isso esperaria até 16h03min04 e, em seguida, executaria algumOutro Comando. Aqui está um exemplo de Linux / Mac de como executar várias solicitações, todas programadas para iniciar ao mesmo tempo:
for request in 1 2 3 4 5 6 7 8 9 10 do tarry -until=16:03:04 && date > results.$request & done
Os binários do Ubuntu, Linux e Windows estão disponíveis por meio de links na página.
fonte