Bash: Dormir jusqu’à une heure / date spécifique

Je veux que mon script bash dorme jusqu’à une heure précise. Donc, je veux une commande comme “sumil” qui ne prend pas d’intervalle mais une heure de fin et dort jusque-là.

Le “at” -daemon n’est pas une solution, car je dois bloquer un script en cours d’exécution jusqu’à une certaine date / heure.

Y a-t-il une telle commande?

Comme mentionné par Outlaw Programmer, je pense que la solution est juste de dormir pour le bon nombre de secondes.

Pour ce faire dans bash, procédez comme suit:

current_epoch=$(date +%s) target_epoch=$(date -d '01/01/2010 12:00' +%s) sleep_seconds=$(( $target_epoch - $current_epoch )) sleep $sleep_seconds 

Pour append de la précision aux nanosecondes (effectivement plus de quelques millisecondes), utilisez par exemple cette syntaxe:

 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 

Notez que macOS / OS X ne prend pas en charge la précision en dessous de secondes, vous devrez utiliser coreutils partir de l’ brew place → voir ces instructions

Utilisez le mode sleep , mais calculez le temps en utilisant la date . Vous voudrez utiliser date -d pour cela. Par exemple, supposons que vous vouliez attendre la semaine prochaine:

 expr `date -d "next week" +%s` - `date -d "now" +%s` 

Il suffit de remplacer “semaine prochaine” par la date que vous souhaitez attendre, puis d’atsortingbuer cette expression à une valeur et de dormir pendant plusieurs secondes:

 startTime=$(date +%s) endTime=$(date -d "next week" +%s) timeToWait=$(($endTime- $startTime)) sleep $timeToWait 

Terminé!

Comme cette question a été posée il y a 4 ans, cette première partie concerne les anciennes versions de bash:

Méthode générale

Afin de réduire les forks , au lieu d’utiliser deux fois la date , je préfère utiliser ceci:

 sleep $(($(date -f - +%s- <<< $'tomorrow 21:30\nnow')0)) 

tomorrow 21:30 pourrait être remplacé par n'importe quel type de date et format reconnu par date , dans le futur.

ou mieux, pour atteindre le prochain HH:MM ce qui signifie aujourd'hui si possible, demain si trop tard:

 sleep $((($(date -f - +%s- <<<$'21:30 tomorrow\nnow')0)%86400)) 

Cela fonctionne sous bash , ksh et autres shells modernes, mais vous devez utiliser:

 sleep $(( ( $(printf 'tomorrow 21:30\nnow\n' | date -f - +%s-)0 )%86400 )) 

sous des coquilles plus légères comme la cendre ou le tiret .

Nouvelle façon de faire (pas de fourchette)

Comme mon besoin pour ce type d’outil n’a jamais dépassé 24 heures, ce qui suit ne concerne que HH , HH:MM ou HH:MM:SS signification de la syntaxe dans les prochaines 24 heures . (De toute façon, si vous avez besoin de plus, vous pourriez même revenir à l'ancienne méthode en utilisant un fork à date . Essayer d'éliminer un fork dans un script s'exécutant sur plusieurs jours est excessif.)

Comme les nouvelles versions de bash proposent une option printf pour récupérer la date, pour cette nouvelle façon de dormir jusqu'à HH: MM sans utiliser de date ni aucun autre fork, j'ai créé une petite fonction bash . C'est ici:

 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 } 

Alors:

 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 

Temps HiRes avec bash sous GNU / Linux

Sous le kernel Linux récent, vous trouverez un fichier de variables nommé /proc/timer_list lequel vous pouvez lire un offset et une variable now , en nanosecondes . Nous pouvons donc calculer le temps de sumil pour atteindre le temps le plus souhaité.

(J'ai écrit ceci pour générer et suivre des événements spécifiques sur de très gros fichiers journaux, contenant des milliers de lignes pendant une seconde).

 mapfile  %(%c)T\n' $slp $((now+${slp%.*}+1)) sleep $slp } 

Après avoir défini deux variables en lecture seule , TIMER_LIST_OFFSET et TIMER_LIST_SKIP , la fonction accède très rapidement au fichier de variable /proc/timer_list pour calculer le temps de repos:

 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 

et enfin

Petite fonction de test

 tstSleepUntilHires () { local now next last printf -v now "%(%s)T" printf -v next "%(%H:%M:%S)T" $((now+1)) printf -v last "%(%H:%M:%S)T" $((now+2)) sleepUntilHires $next date -f - +%F-%T.%N < <(echo now;sleep .92;echo now) sleepUntilHires $last date +%F-%T.%N } 

Peut rendre quelque chose comme:

 sleep 0.155579469s, -> Mon Aug 20 20:42:51 2018 2018-08-20-20:42:51.005743047 2018-08-20-20:42:51.927112981 sleep 0.071764300s, -> Mon Aug 20 20:42:52 2018 2018-08-20-20:42:52.003165816 
  • Au début de la seconde suivante,
  • temps d'impression, alors
  • attendre 0.92 secondes, puis
  • temps d'impression, alors
  • calculer 0,07 seconde pour la seconde suivante
  • dormir 0,07 secondes, puis
  • temps d'impression

Nota: .92 + 0.071 = .991 (sur mon bureau)

Vous pouvez arrêter l’exécution d’un processus en lui envoyant un signal SIGSTOP, puis le faire reprendre en lui envoyant un signal SIGCONT.

Vous pouvez donc arrêter votre script en envoyant un SIGSTOP:

 kill -SIGSTOP  

Et puis utilisez le at deamon pour le réveiller en lui envoyant un SIGCONT de la même manière.

Vraisemblablement, votre script indiquera à quel moment il voulait être réveillé avant de s’endormir.

Pour suivre la réponse de SpoonMeiser, voici un exemple spécifique:

 $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 $ 

Je voulais un script qui vérifiait uniquement les heures et les minutes pour pouvoir exécuter le script avec les mêmes parameters chaque jour. Je ne veux pas m’inquiéter de quel jour sera demain. J’ai donc utilisé une approche différente.

 target="$1.$2" cur=$(date '+%H.%M') while test $target != $cur; do sleep 59 cur=$(date '+%H.%M') done 

les parameters du script sont les heures et les minutes, donc je peux écrire quelque chose comme:

 til 7 45 && mplayer song.ogg 

(til est le nom du script)

plus de jours de retard au travail parce que vous avez mal écrit le jour. à votre santé!

Sur Ubuntu 12.04.4 LTS, voici la simple entrée bash qui fonctionne:

 sleep $(expr `date -d "03/21/2014 12:30" +%s` - `date +%s`) 

timeToWait = $ (($ end – $ start))

Attention, “timeToWait” pourrait être un nombre négatif! (par exemple, si vous spécifiez pour dormir jusqu’à “15:57” et maintenant c’est “15:58”). Donc, vous devez le vérifier pour éviter les erreurs de message étranges:

 #!/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 

Voici une solution qui fait le travail ET informe l’utilisateur du temps restant. Je l’utilise presque tous les jours pour exécuter des scripts pendant la nuit (en utilisant cygwin, car je ne pouvais pas faire fonctionner cron sur Windows)

Caractéristiques

  • Précis jusqu’à la seconde
  • Détecte les changements d’heure du système et s’adapte
  • Sortie intelligente indiquant combien de temps il rest
  • Format d’entrée 24 heures
  • renvoie true pour pouvoir enchaîner avec &&

Échantillon échantillon

 $ 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 date à la fin ne fait pas partie de la fonction, mais en raison de la && date )

Code

 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 } 

Vous pouvez calculer le nombre de secondes entre maintenant et l’heure de réveil et utiliser la commande “veille” existante.

Vous pourriez peut-être utiliser ‘at’ pour envoyer un signal à votre script, qui attendait ce signal.

J’ai mis en place un petit utilitaire appelé Hypnos pour ce faire. Il est configuré à l’aide de la syntaxe crontab et bloque jusqu’à cette date.

 #!/bin/bash while [ 1 ]; do hypnos "0 * * * *" echo "running some tasks..." # ... done 

Voici quelque chose que je viens d’écrire pour synchroniser plusieurs clients de test:

 #!/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) 

Ce script dort jusqu'à la prochaine heure "arrondie" ou "forte".

Un cas d'utilisation simple consiste à exécuter ./sleep.py 10; ./test_client1.py ./sleep.py 10; ./test_client1.py dans un terminal et ./sleep.py 10; ./test_client2.py ./sleep.py 10; ./test_client2.py dans un autre.

Sur OpenBSD , les éléments suivants pourraient être utilisés pour compacter un travail crontab(5) minutes */5 en un horaire de 00 heure (pour vous assurer que moins d’e-mails sont générés, tout en effectuant la même tâche à des intervalles précis):

 #!/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 

Notez que la date(1) interromprait également la mise en sleep(1) lors de l’itération finale, car 60 minutes ne sont pas une heure valide (sauf si c’est le cas!). obtenir notre rapport de courrier électronique.

Notez également que si l’une des itérations prend plus de 5 minutes, le sleep échoue de manière gracieuse en ne dormant pas du tout (à cause de ce qui est un nombre négatif interprété comme une option de ligne de commande, au lieu de à l’heure suivante ou même l’éternité), s’assurant ainsi que votre travail pourrait encore se terminer dans l’heure allouée (par exemple, si une seule itération prend un peu plus de 5 minutes, nous aurions toujours le temps de nous rattraper, sans rien envelopper autour de l’heure suivante).

Le printf(1) est nécessaire car la date attend exactement deux chiffres pour la spécification des minutes.

  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 

J’ai écrit https://tamentis.com/projects/sleepuntil/ dans ce but précis. C’est un peu excessif, la plupart du code provient de BSD ‘at’, donc il est assez conforme aux normes:

 $ sleepuntil noon && sendmail something