Il y a une commande Unix intégrée repeat
dont le premier argument est le nombre de répétitions d'une commande, où la commande (avec tous les arguments) est spécifiée par les arguments restants à repeat
.
Par exemple,
% repeat 100 echo "I will not automate this punishment."
fera écho à la chaîne donnée 100 fois puis s'arrêtera.
J'aimerais une commande similaire - appelons-la forever
- qui fonctionne de la même manière, sauf que le premier argument est le nombre de secondes pour faire une pause entre les répétitions, et il se répète pour toujours. Par exemple,
% forever 5 echo "This will get echoed every 5 seconds forever and ever."
J'ai pensé demander si une telle chose existe avant de l'écrire. Je sais que c'est comme un Perl à 2 lignes ou un script Python, mais il y a peut-être une façon plus standard de le faire. Sinon, n'hésitez pas à publier une solution dans votre langage de script préféré.
PS: Peut-être qu'une meilleure façon de procéder serait de généraliser repeat
pour prendre à la fois le nombre de répétitions (avec -1 signifiant l'infini) et le nombre de secondes pour dormir entre les répétitions. Les exemples ci-dessus deviendraient alors:
% repeat 100 0 echo "I will not automate this punishment."
% repeat -1 5 echo "This will get echoed every 5 seconds forever."
Essayez la commande watch
.
Usage: watch [-dhntv] [--differences[=cumulative]] [--help] [--interval=<n>]
[--no-title] [--version] <command>`
Pour que:
watch -n1 command
exécutera la commande toutes les secondes (enfin, techniquement, chaque seconde plus le temps nécessaire à command
pour s'exécuter en tant que watch
(au moins les procps
et busybox
implémentations) ne dort qu'une seconde entre deux exécutions de command
), pour toujours.
Souhaitez-vous passer la commande à exec
au lieu de sh -c
, utilisation -x
option:
watch -n1 -x command
Sur macOS, vous pouvez obtenir watch
à partir de Ports Mac :
port install watch
Ou vous pouvez l'obtenir auprès de Homebrew :
brew install watch
Bash
while
+ sleep
:
while true
do
echo "Hi"
sleep 1
done
Voici la même chose qu'un raccourci à une ligne (d'après les commentaires ci-dessous):
while sleep 1; do echo "Hi"; done
Les usages ;
pour séparer les commandes et utilise sleep 1
pour le test while
car il renvoie toujours vrai. Vous pouvez mettre plus de commandes dans la boucle - il suffit de les séparer avec ;
Ceci est juste une version plus courte d'autres while+sleep
réponses, si vous exécutez ce type de tâches souvent comme routine quotidienne, son utilisation vous évite d'appuyer sur des touches inutiles, et si votre ligne de commande commence à être plus longue, celle-ci est un peu plus facile. Mais celui-ci commence par dormir d'abord.
Ceci est généralement utile si vous devez suivre quelque chose qui a une sortie en ligne comme la charge de la machine:
while sleep 1; do uptime; done
Un problème que toutes les réponses publiées jusqu'à présent ont est que le temps d'exécution de la commande peut dériver. Par exemple, si vous effectuez une sleep 10
entre les commandes, et la commande prend 2 secondes pour s'exécuter, puis elle va s'exécuter toutes les 12 secondes; s'il faut un temps variable pour s'exécuter, alors à long terme, le moment où il s'exécute peut être imprévisible.
Cela pourrait être exactement ce que vous voulez; si c'est le cas, utilisez l'une des autres solutions, ou utilisez celle-ci mais simplifiez l'appel sleep
.
Pour une résolution d'une minute, les tâches cron s'exécutent à l'heure spécifiée, quelle que soit la durée de chaque commande. (En fait, une nouvelle tâche cron sera lancée même si la précédente est toujours en cours d'exécution.)
Voici un script Perl simple qui dort jusqu'à l'intervalle suivant, donc par exemple avec un intervalle de 10 secondes, la commande peut s'exécuter à 12:34:00, 12:34:10, 12:34:20, etc., même si le la commande elle-même prend plusieurs secondes. Si la commande s'exécute plus de interval
secondes, l'intervalle suivant sera ignoré (contrairement à cron). L'intervalle est calculé par rapport à l'époque, donc un intervalle de 86400 secondes (1 jour) s'exécutera à minuit UTC.
#!/usr/bin/Perl
use strict;
use warnings;
if (scalar @ARGV < 2) {
die "Usage: $0 seconds command [args...]\n";
}
$| = 1; # Ensure output appears
my($interval, @command) = @ARGV;
# print ">>> interval=$interval command=(@command)\n";
while (1) {
print "sleep ", $interval - time % $interval, "\n";
sleep $interval - time % $interval;
system @command; # TODO: Handle errors (how?)
}
Je pense que jusqu'à présent, toutes les réponses sont soit trop compliquées, soit répondent à une autre question:
La vraie question était:
Ce sont deux choses très différentes lorsque la commande prend du temps pour se terminer.
Prenez par exemple le script foo.sh
(faites comme s'il s'agissait d'un programme qui prend quelques secondes).
#!/bin/bash
# foo.sh
echo `date +"%H:%M:%S"` >> output.txt;
sleep 2.5;
# ---
Vous souhaitez l'exécuter toutes les secondes, et la plupart suggèrent watch -n1 ./foo.sh
, ou while sleep 1; do ./foo.sh; done
. Cependant, cela donne la sortie:
15:21:20
15:21:23
15:21:27
15:21:30
15:21:34
Ce qui n'est pas exactement exécuté toutes les secondes. Même avec le -p
flag, comme man watch
la page suggère que cela pourrait résoudre ce problème, le résultat est le même.
Un moyen facile d'accomplir la tâche souhaitée, que certains abordent, consiste à exécuter la commande en arrière-plan. En d'autres termes:
while sleep 1; do (./foo.sh &) ; done
Et c'est tout ce qu'il y a à faire.
Vous pouvez l'exécuter toutes les 500 ms avec sleep 0.5
, ou qu'avez-vous.
Dans bash
:
bash -c 'while [ 0 ]; do echo "I will not automate this punishment in absurdum."; done'
(echo
pourrait être remplacé par n'importe quelle commande ...
Ou dans Perl
:
Perl -e 'for (;1;) {print "I will not automate this punishment in absurdum.\n"}'
Où print "I will not automate this punishment in absurdum.\n"
pourrait être remplacé par la commande "any" entourée de guillemets (`).
Et pour une pause, ajoutez une instruction sleep
à l'intérieur de la boucle for:
bash -c 'while [ 0 ]; do echo "I will not automate this punishment in absurdum."; sleep 1; done'
et
Perl -e 'for (;1;) {print "I will not automate this punishment in absurdum.\n"; sleep 1}'
Afin de limiter le temps d'exécution, il n'y a pas de fourches ! Seuls les intégrés sont utilisés.
Pour cela, j'utilise la fonction intégrée read
au lieu de sleep
. Malheureusement, cela ne fonctionnera pas avec les sessions notty.
repeat
" comme demandé:repeat () {
local repeat_times=$1 repeat_delay=$2 repeat_foo repeat_sleep
read -t .0001 repeat_foo
if [ $? = 1 ] ;then
repeat_sleep() { sleep $1 ;}
else
repeat_sleep() { read -t $1 repeat_foo; }
fi
shift 2
while ((repeat_times)); do
((repeat_times=repeat_times>0?repeat_times-1:repeat_times))
"${@}"
((repeat_times))&& ((10#${repeat_delay//.})) &&
repeat_sleep $repeat_delay
done
}
Petit test avec des chaînes entre guillemets:
repeat 3 0 printf "Now: %(%T)T, Hello %s.\n" -1 Guy
Now: 15:13:43, Hello Guy.
Now: 15:13:43, Hello Guy.
Now: 15:13:43, Hello Guy.
repeat -1 .5 printf "Now: %(%T)T, Hello %s.\n" -1 Guy
Now: 15:14:14, Hello Guy.
Now: 15:14:14, Hello Guy.
Now: 15:14:15, Hello Guy.
Now: 15:14:15, Hello Guy.
Now: 15:14:16, Hello Guy.
Now: 15:14:16, Hello Guy.
Now: 15:14:17, Hello Guy.
Now: 15:14:17, Hello Guy.
Now: 15:14:18, Hello Guy.
Now: 15:14:18, Hello Guy.
^C
Sous les noyaux Linux récents, il y a un procfile /proc/timer_list
contenant des informations temporelles en nanosecondes.
Si vous souhaitez exécuter une commande exactement une fois par seconde, votre commande doit se terminer en moins d'une seconde! Et à partir de là, vous devez sleep
uniquement le reste de seconde actuelle.
Si le délai est plus important et que votre commande ne nécessite pas beaucoup de temps, vous pouvez:
command=(echo 'Hello world.')
delay=10
while :;do
printf -v now "%(%s)T" -1
read -t $(( delay-(now%delay) )) foo
${command[@]}
done.
Mais si votre objectif est d'obtenir une granularité plus fine, vous devez:
Pour cela, j'ai écrit une petite fonction bash:
# bash source file for nano wait-until-next-second
mapfile </proc/timer_list _timer_list
for ((_i=0;_i<${#_timer_list[@]};_i++));do
((_c+=${#_timer_list[_i]}))
[[ ${_timer_list[_i]} =~ ^now ]] && TIMER_LIST_READ=$_c
[[ ${_timer_list[_i]} =~ offset:.*[1-9] ]] && \
TIMER_LIST_OFFSET=${_timer_list[_i]//[a-z.: ]} && \
break
done
unset _i _timer_list _c
readonly TIMER_LIST_OFFSET TIMER_LIST_READ
waitNextSecondHires() {
local nsnow nsslp
read -N$TIMER_LIST_READ nsnow </proc/timer_list
nsnow=${nsnow%% nsecs*}
nsnow=$((${nsnow##* }+TIMER_LIST_OFFSET))
nsslp=$((2000000000-10#${nsnow:${#nsnow}-9}))
read -t .${nsslp:1} foo
}
Après les avoir achetés, vous pouvez:
command=(echo 'Hello world.')
while :;do
waitNextSecondHires
${command[@]}
done.
courir ${command[@]}
directement sur la ligne de commande, que comparer à
command=(eval "echo 'Hello world.';sleep .3")
while :;do
waitNextSecondHires
${command[@]}
done.
cela doit donner exactement le même résultat.
repeat
" comme demandé:Vous pouvez vous procurer ceci:
mapfile </proc/timer_list _timer_list
for ((_i=0;_i<${#_timer_list[@]};_i++));do
((_c+=${#_timer_list[_i]}))
[[ ${_timer_list[_i]} =~ ^now ]] && TIMER_LIST_READ=$_c
[[ ${_timer_list[_i]} =~ offset:.*[1-9] ]] && \
TIMER_LIST_OFFSET=${_timer_list[_i]//[a-z.: ]} && \
break
done
unset _i _timer_list _c
readonly TIMER_LIST_OFFSET TIMER_LIST_READ
repeat_hires () {
local repeat_times=$1 repeat_delay=$2 repeat_foo repeat_sleep repeat_count
read -t .0001 repeat_foo
if [ $? = 1 ] ;then
repeat_sleep() { sleep $1 ;}
else
repeat_sleep() { read -t $1 repeat_foo; }
fi
shift 2
printf -v repeat_delay "%.9f" $repeat_delay
repeat_delay=${repeat_delay//.}
read -N$TIMER_LIST_READ nsnow </proc/timer_list
nsnow=${nsnow%% nsec*}
started=${nsnow##* }
while ((repeat_times)); do
((repeat_times=repeat_times>0?repeat_times-1:repeat_times))
"${@}"
((repeat_times)) && ((10#$repeat_delay)) && {
read -N$TIMER_LIST_READ nsnow </proc/timer_list
nsnow=${nsnow%% nsec*}
nsnow=${nsnow##* }
(( (nsnow - started) / 10#$repeat_delay - repeat_count++ )) &&
printf >&2 "WARNING: Command '%s' too long for %f delay.\n" \
"${*}" ${repeat_delay:0:${#repeat_delay}-9
}.${repeat_delay:${#repeat_delay}-9}
printf -v sleep "%010d" $((
10#$repeat_delay - ( ( nsnow - started ) % 10#$repeat_delay ) ))
repeat_sleep ${sleep:0:${#sleep}-9}.${sleep:${#sleep}-9}
}
done
}
Alors essayez-le:
time repeat_hires 21 .05 sh -c 'date +%s.%N;sleep .01'
1480867565.152022457
1480867565.201249108
1480867565.251333284
1480867565.301224905
1480867565.351236725
1480867565.400930482
1480867565.451207075
1480867565.501212329
1480867565.550927738
1480867565.601199721
1480867565.651500618
1480867565.700889792
1480867565.750963074
1480867565.800987954
1480867565.853671458
1480867565.901232296
1480867565.951171898
1480867566.000917199
1480867566.050942638
1480867566.101171249
1480867566.150913407
real 0m1.013s
user 0m0.000s
sys 0m0.016s
time repeat_hires 3 .05 sh -c 'date +%s.%N;sleep .05'
1480867635.380561067
WARNING: Command 'sh -c date +%s.%N;sleep .05' too long for 0.050000 delay.
1480867635.486503367
WARNING: Command 'sh -c date +%s.%N;sleep .05' too long for 0.050000 delay.
1480867635.582332617
real 0m0.257s
user 0m0.000s
sys 0m0.004s
Bash et certains de ses obus apparentés ont la commodité (( ... ))
notation dans laquelle les expressions arithmétiques peuvent être évaluées.
Donc, pour répondre à votre troisième défi, où le nombre de répétitions et le délai entre chaque répétition doivent être configurables, voici une façon de le faire:
repeat=10
delay=1
i=0
while (( i++ < repeat )); do
echo Repetition $i
sleep $delay
done
Cette réponse souffre également de la dérive temporelle couverte par la réponse de Keith .
Si votre intention n'est pas d'afficher un message sur votre écran et si vous pouviez vous permettre de répéter le travail en minutes, crontab , peut-être, serait votre meilleur outil. Par exemple, si vous souhaitez exécuter votre commande toutes les minutes, vous écririez quelque chose comme ceci dans votre fichier crontab
:
* * * * * my_precious_command
Veuillez consulter le tutoriel pour plus d'exemples. En outre, vous pouvez définir les horaires facilement en utilisant Générateur de code Crontab .
#!/usr/bin/env Perl
# First argument is number of seconds to sleep between repeats, remaining
# arguments give the command to repeat forever.
$sleep = shift;
$cmd = join(' ', @ARGV);
while(1) {
system($cmd);
sleep($sleep);
}
Vous voulez essayer ça (Bash)?
forever () {
TIMES=shift;
SLEEP=shift;
if [ "$TIMES" = "-1" ]; then
while true;
do
$@
sleep $SLEEP
done
else
repeat "$TIMES" $@
fi; }
Comme mentionné par gbrandt, si la commande watch
est disponible, utilisez-la définitivement. Certains systèmes Unix, cependant, ne l'ont pas installé par défaut (du moins, ils ne le font pas où je travaille).
Voici une autre solution avec une syntaxe et une sortie légèrement différentes (fonctionne en BASH et SH):
while [ 1 ] ; do
<cmd>
sleep <x>
echo ">>>>>>>>>>>>>" `date` ">>>>>>>>>>>>>>"
done
Edit: j'ai supprimé certains "." dans la dernière déclaration d'écho ... reliquat de mes jours en Perl;)
Et si nous avions les deux?
Voici l'idée: avec seulement "intervalle", il se répète pour toujours. Avec "intervalle" et "fois", il répète ce nombre de fois, séparés par "intervalle".
L'usage :
$ loop [interval [times]] command
Ainsi, l'algorithme sera:
Par conséquent :
loop() {
local i=2 t=1 cond
[ -z ${1//[0-9]/} ] && i=$1 && shift
[ -z ${1//[0-9]/} ] && t=$1 && shift && cond=1
while [ $t -gt 0 ]; do
sleep $i
[ $cond ] && : $[--t]
$@
done
}
J'ai fini par créer une variante de la réponse de swalog. Avec le sien, il a fallu attendre X secondes pour la première itération, je passe aussi mon premier plan donc ..
./foo.sh;while sleep 1; do (./foo.sh) ; done
Vous pouvez obtenir la puissance de l'interpréteur python de Shell.
python3 -c "import time, os
while True:
os.system('your command')
time.sleep(5)
remplacer cinq en tout temps (sec) que vous aimez.
Attention: le retour utilise SHIFT + ENTER pour retourner une entrée dans Shell. Et n'oubliez pas de taper 4 ESPACE indentation dans chaque ligne de la boucle while.
Un moyen facile de répéter un travail depuis crontab avec un intervalle inférieur à une minute (exemple de 20 secondes):
crontab: * * * * * script.sh
script.sh:
#!/bin/bash
>>type your commands here.
sleep 20
>>retype your commands here.
sleep 20
>>retype your commands here.
Vous pouvez exécuter un script depuis init (en ajoutant une ligne à/etc/inittab). Ce script doit exécuter votre commande, s'endormir pendant le temps que vous souhaitez attendre jusqu'à ce que vous exécutiez à nouveau le script et le quitter. Init relancera votre script après sa sortie.
Rapide, sale et probablement dangereux pour démarrer, mais si vous êtes aventureux et savez ce que vous faites, mettez ceci dans repeat.sh
et chmod 755
il,
while true
do
eval $1
sleep $2
done
Invoquez-le avec ./repeat.sh <command> <interval>
Mon sens spidey dit que c'est probablement une mauvaise façon de faire cela, mon sens spidey est-il vrai?
#! /bin/sh
# Run all programs in a directory in parallel
# Usage: run-parallel directory delay
# Copyright 2013 by Marc Perkel
# docs at http://wiki.junkemailfilter.com/index.php/How_to_run_a_Linux_script_every_few_seconds_under_cron"
# Free to use with attribution
if [ $# -eq 0 ]
then
echo
echo "run-parallel by Marc Perkel"
echo
echo "This program is used to run all programs in a directory in parallel"
echo "or to rerun them every X seconds for one minute."
echo "Think of this program as cron with seconds resolution."
echo
echo "Usage: run-parallel [directory] [delay]"
echo
echo "Examples:"
echo " run-parallel /etc/cron.20sec 20"
echo " run-parallel 20"
echo " # Runs all executable files in /etc/cron.20sec every 20 seconds or 3 times a minute."
echo
echo "If delay parameter is missing it runs everything once and exits."
echo "If only delay is passed then the directory /etc/cron.[delay]sec is assumed."
echo
echo 'if "cronsec" is passed then it runs all of these delays 2 3 4 5 6 10 12 15 20 30'
echo "resulting in 30 20 15 12 10 6 5 4 3 2 executions per minute."
echo
exit
fi
# If "cronsec" is passed as a parameter then run all the delays in parallel
if [ $1 = cronsec ]
then
$0 2 &
$0 3 &
$0 4 &
$0 5 &
$0 6 &
$0 10 &
$0 12 &
$0 15 &
$0 20 &
$0 30 &
exit
fi
# Set the directory to first prameter and delay to second parameter
dir=$1
delay=$2
# If only parameter is 2,3,4,5,6,10,12,15,20,30 then automatically calculate
# the standard directory name /etc/cron.[delay]sec
if [[ "$1" =~ ^(2|3|4|5|6|10|12|15|20|30)$ ]]
then
dir="/etc/cron.$1sec"
delay=$1
fi
# Exit if directory doesn't exist or has no files
if [ ! "$(ls -A $dir/)" ]
then
exit
fi
# Sleep if both $delay and $counter are set
if [ ! -z $delay ] && [ ! -z $counter ]
then
sleep $delay
fi
# Set counter to 0 if not set
if [ -z $counter ]
then
counter=0
fi
# Run all the programs in the directory in parallel
# Use of timeout ensures that the processes are killed if they run too long
for program in $dir/* ; do
if [ -x $program ]
then
if [ "0$delay" -gt 1 ]
then
timeout $delay $program &> /dev/null &
else
$program &> /dev/null &
fi
fi
done
# If delay not set then we're done
if [ -z $delay ]
then
exit
fi
# Add delay to counter
counter=$(( $counter + $delay ))
# If minute is not up - call self recursively
if [ $counter -lt 60 ]
then
. $0 $dir $delay &
fi
# Otherwise we're done
... Je me demande combien de solutions compliquées peuvent être créées pour résoudre ce problème.
Cela peut être si facile ...
open /etc/crontab
y mettre 1 ligne suivante à la fin du fichier comme:
*/NumberOfSeconds * * * * user /path/to/file.sh
Si vous voulez exécuter quelque chose toutes les 1 seconde, mettez-le simplement ici:
*/60 * * * * root /path/to/file.sh
where that file.sh could be chmod 750 /path/to/file.sh
et à l'intérieur de ce fichier.sh devrait être:
#!/bin/bash
#What does it do
#What is it runned by
your code or commands
et c'est tout!
PRENDRE PLAISIR!
Avec zsh
et une implémentation sleep
qui accepte les arguments à virgule flottante:
typeset -F SECONDS=0 n=0
repeat 100 {cmd; sleep $(((n+=3) - SECONDS))}
Ou pour forever
:
for ((;;)) {cmd; sleep $(((n+=3) - SECONDS))}
Si votre sleep
ne prend pas en charge les flottants, vous pouvez toujours le redéfinir comme un wrapper autour du zsh
de zselect
:
zmodload zsh/zselect
sleep() zselect -t $((($1 * 100) | 0))
Vous pouvez trouver cette fonction récursive:
#!/bin/bash
ininterval () {
delay=$1
shift
$*
sleep $delay
ininterval $delay $*
}
ou ajoutez un:
ininterval $*
et appelez le script.
Pour exécuter plusieurs fois une commande dans une fenêtre de console, j'exécute généralement quelque chose comme ceci:
while true; do (run command here); done
Cela fonctionne également pour plusieurs commandes, par exemple, pour afficher une horloge constamment mise à jour dans une fenêtre de console:
while true; do clear; date; sleep 1; done
Cette solution fonctionne dans MacOSX 10.7. Cela fonctionne à merveille.
bash -c 'while [ 0 ]; do \
echo "I will not automate this punishment in absurdum."; done'
Dans mon cas
bash -c 'while [ 0 ]; do ls; done'
ou
bash -c 'while [ 0 ]; do mv "Desktop/* Documents/Cleanup"; done'
pour nettoyer mon bureau en permanence.