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?
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
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é!
Comme cette question a été posée il y a 4 ans, cette première partie concerne old versions bash:
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))
où 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 .
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
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
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
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 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.
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`)
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 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é!
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)
&&
$ 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
)
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
}
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
Vous pouvez calculer le nombre de secondes entre maintenant et l'heure de réveil et utiliser la commande "sommeil" existante.
Vous pourriez peut-être utiliser "at" pour envoyer un signal à votre script, qui attendait ce signal.
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 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
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.
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.
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