web-dev-qa-db-fra.com

Bash: dormir jusqu'à une heure / date précise

Je veux que mon script bash reste en veille jusqu'à une heure précise. Donc, je veux une commande comme "sleep" qui ne prend pas d'intervalle mais une heure de fin et qui dort jusque-là.

Le démon "at" n'est pas une solution, car j'ai besoin de bloquer un script en cours jusqu'à une certaine date/heure.

Existe-t-il une telle commande?

72
theomega

Comme mentionné par Outlaw Programmer, je pense que la solution est juste de dormir pendant 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 ajouter de la précision aux nanosecondes (en fait plus autour des millisecondes), utilisez par ex. 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 quelques secondes, vous devez utiliser coreutils de brew à la place → voir ces instructions

81
SpoonMeiser

Utilisez sleep, mais calculez l'heure à l'aide de date. Vous voudrez utiliser date -d pour ça. Par exemple, supposons que vous vouliez attendre la semaine prochaine:

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

Remplacez simplement "la semaine prochaine" par la date que vous souhaitez attendre, puis affectez cette expression à une valeur et dormez pendant autant de secondes:

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

Terminé!

19
John Feminella

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

Méthode générale

Afin de réduire forks, au lieu d'exécuter date deux fois, je préfère utiliser ceci:

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

tomorrow 21:30 pourrait être remplacé par tout type de date et de format reconnu par date, à l'avenir.

ou mieux, pour atteindre suivant HH:MM signifiant aujourd'hui si possible, demain si trop tard:

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

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

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

sous plus léger des coquilles comme ash ou dash .

Nouveau bash façon (pas de fourche)

Comme mon besoin pour ce type d'outil n'a jamais dépassé 24 heures, les éléments suivants ne concerneront que HH, HH:MM ou HH:MM:SS syntaxe signifiant dans les prochaines 24 heures. (Quoi qu'il en soit, si vous en avez besoin de plus, vous pouvez même revenir à l'ancienne méthode en utilisant un fork pour date. Essayer d'éliminer un fork dans un script exécuté sur plusieurs jours est exagéré.)

Comme les nouvelles versions de bash offrent une option printf pour récupérer la date, pour cette nouvelle façon de dormir jusqu'à HH: MM sans utiliser date ou tout autre fork, j'ai construit 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
}

Ensuite:

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 noyau Linux récent, vous trouverez un fichier de variables nommé /proc/timer_list où vous pouvez lire une variable offset et now, en nanosecondes . Nous pouvons donc calculer le temps de sommeil pour atteindre le tout en haut temps 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  </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
}

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

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, puis
  • attendez 0,92 seconde, puis
  • temps d'impression, puis
  • calculer 0,07 seconde à gauche, jusqu'à la seconde suivante
  • dormir 0,07 secondes, puis
  • temps d'impression.

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

13
F. Hauri

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

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

kill -SIGSTOP <pid>

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

Vraisemblablement, votre script vous informera du moment où il a voulu être réveillé avant de s'endormir.

8
SpoonMeiser

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`)
7
Anthony O.

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
$
6
Dennis Williamson

Je voulais un script qui ne vérifiait que les heures et les minutes pour que je puisse exécuter le script avec les mêmes paramètres tous les jours. Je ne veux pas m'inquiéter du jour qui 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 paramètres du script sont les heures et les minutes, donc je peux écrire quelque chose comme:

til 7 45 && mplayer song.ogg

(c'est le nom du script)

plus de jours de retard au travail car vous avez mal tapé la journée. à votre santé!

4
kolomer

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)

Traits

  • Précis jusqu'au deuxième
  • Détecte les changements d'heure système et s'adapte
  • Sortie intelligente indiquant combien de temps il reste
  • Format d'entrée 24 heures
  • renvoie true pour pouvoir chaîner avec &&

Exemple d'exécution

$ 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 && 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
}
4
Camusensei

timeToWait = $ (($ fin - $ début))

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

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

Vous pouvez calculer le nombre de secondes entre maintenant et l'heure de réveil et utiliser la commande "sommeil" existante.

1
Outlaw Programmer

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

1
Kim Reece
 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
1
Nick Constantine

J'ai mis en place un petit utilitaire appelé Hypnos pour ce faire. Il est configuré à l'aide de la syntaxe et des blocs crontab jusqu'à ce moment.

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

Sur OpenBSD , ce qui suit pourrait être utilisé pour compacter un */5 5 minutes crontab(5) travail dans un 00 horaire (pour vous assurer que moins d'e-mails sont générés, tout en effectuant la même tâche à intervalles exacts):

#!/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 le date(1) briserait également le sleep(1) par conception à l'itération finale, comme 60 Minutes n'est pas une heure valide (sauf si c'est le cas!), nous n'aurons donc pas à attendre plus longtemps avant d'obtenir notre rapport par e-mail.

Notez également que si l'une des itérations prend plus de 5 minutes, le sleep échouerait également gracieusement par conception en ne dormant pas du tout (en raison de ce qui est un nombre négatif interprété comme une option de ligne de commande , au lieu de passer à l'heure suivante ou même à l'éternité), garantissant ainsi que votre travail pourrait encore se terminer dans l'heure allouée (par exemple, si une seule des itérations prend un peu plus de 5 minutes, nous aurions toujours le le temps de rattraper son retard, sans rien enrouler à l'heure suivante).

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

0
cnst

Voici quelque chose que j'ai écrit tout à l'heure 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 s'endort jusqu'à la prochaine heure "arrondie" ou "nette".

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

0
Dima Tisnek

J'ai écrit https://tamentis.com/projects/sleepuntil/ dans ce but précis. C'est un peu exagéré la plupart du code provient de BSD 'at' donc il est assez conforme aux standards:

$ sleepuntil noon && sendmail something
0
Bertrand Janin