it-swarm.it

Ripeti un comando Unix ogni x secondi per sempre

C'è un comando Unix incorporato repeat il cui primo argomento è il numero di volte in cui ripetere un comando, dove il comando (con qualsiasi argomento) è specificato dagli argomenti rimanenti su repeat.

Per esempio,

% repeat 100 echo "I will not automate this punishment."

farà eco la stringa data 100 volte e poi si fermerà.

Vorrei un comando simile - chiamiamolo forever - che funziona in modo simile tranne che il primo argomento è il numero di secondi di pausa tra le ripetizioni e si ripete per sempre. Per esempio,

% forever 5 echo "This will get echoed every 5 seconds forever and ever."

Ho pensato di chiedere se esiste una cosa del genere prima di scriverla. So che è come un Perl a 2 righe o Python, ma forse c'è un modo più standard per farlo. Altrimenti, sentiti libero di pubblicare una soluzione nel tuo linguaggio di scripting preferito.

PS: Forse un modo migliore per farlo sarebbe generalizzare repeat per impiegare sia il numero di volte per ripetere (con -1 che significa infinito) sia il numero di secondi per dormire tra le ripetizioni. Gli esempi precedenti diventerebbero quindi:

% repeat 100 0 echo "I will not automate this punishment."
% repeat -1 5 echo "This will get echoed every 5 seconds forever."
497
dreeves

Prova il comando watch.

Usage: watch [-dhntv] [--differences[=cumulative]] [--help] [--interval=<n>] 
             [--no-title] [--version] <command>`

Così che:

watch -n1  command

eseguirà il comando ogni secondo (bene, tecnicamente, ogni secondo più il tempo necessario per l'esecuzione di command come watch (almeno procps e busybox implementazioni) dorme solo un secondo tra due esecuzioni di command), per sempre.

Vorresti passare il comando a exec invece di sh -c, uso -x opzione:

watch -n1 -x command

Su macOS, puoi ottenere watch da Mac Mac :

port install watch

Oppure puoi ottenerlo da Homebrew :

brew install watch
637
Gregor Brandt

Bash

while + sleep:

while true
do 
    echo "Hi"
    sleep 1
done

Ecco la stessa cosa di una scorciatoia one-liner (dai commenti sotto):

while sleep 1; do echo "Hi"; done

Utilizza ; per separare i comandi e usa sleep 1 per il test while poiché restituisce sempre true. Puoi inserire più comandi nel loop: basta separarli con ;

255
pope

Questa è solo una versione più breve di altri while+sleep risponde, se stai eseguendo questo tipo di attività spesso come la tua routine quotidiana, l'utilizzo di questo ti salva da inutili pressioni dei tasti e se la tua riga di comando inizia a capire più a lungo questa è un po 'più semplice. Ma questo inizia prima di dormire.

Questo è generalmente utile se devi seguire qualcosa che ha un output di una riga come il carico della macchina:

while sleep 1; do uptime; done
73
Bekir Dogan

Un problema che tutte le risposte postate finora hanno è che il tempo di esecuzione del comando può andare alla deriva. Ad esempio, se si esegue un sleep 10 tra i comandi e l'esecuzione del comando richiede 2 secondi, quindi verrà eseguita ogni 12 secondi; se l'esecuzione richiede una quantità variabile di tempo, a lungo termine il tempo in cui viene eseguito può essere imprevedibile.

Questo potrebbe essere proprio quello che vuoi; in tal caso, utilizzare una delle altre soluzioni oppure utilizzare questa ma semplificando la chiamata sleep.

Per una risoluzione di un minuto, i processi cron verranno eseguiti all'ora specificata, indipendentemente dalla durata di ciascun comando. (In effetti un nuovo cron job verrà avviato anche se il precedente è ancora in esecuzione.)

Ecco un semplice script Perl che dorme fino all'intervallo successivo, quindi ad esempio con un intervallo di 10 secondi il comando potrebbe essere eseguito alle 12:34:00, 12:34:10, 12:34:20, ecc., Anche se il il comando stesso richiede alcuni secondi. Se il comando viene eseguito per più di interval secondi, l'intervallo successivo verrà ignorato (diversamente dal cron). L'intervallo viene calcolato rispetto all'epoca, quindi un intervallo di 86400 secondi (1 giorno) verrà eseguito a mezzanotte 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?)
}
46
Keith Thompson

Penso che tutte le risposte qui finora siano o troppo contorte, o invece rispondano a una domanda diversa:

  • "Come eseguire ripetutamente un programma in modo che ci siano X secondi di ritardo tra il termine del programma e l'avvio successivo".

La vera domanda era:

  • "Come eseguire un programma ogni X secondi"

Queste sono due cose molto diverse quando il comando impiega tempo per terminare.

Prendi ad esempio lo script foo.sh (fingi che questo sia un programma che richiede alcuni secondi per essere completato).

#!/bin/bash
# foo.sh
echo `date +"%H:%M:%S"` >> output.txt;
sleep 2.5;
# ---

Desideri eseguire questo ogni secondo e la maggior parte suggerisce watch -n1 ./foo.sh, o while sleep 1; do ./foo.sh; done. Tuttavia, questo dà l'output:

15:21:20
15:21:23
15:21:27
15:21:30
15:21:34

Che non viene eseguito esattamente ogni secondo. Anche con il -p flag, come man watch pagina suggerisce che potrebbe risolvere questo problema, il risultato è lo stesso.

Un modo semplice per eseguire l'attività desiderata, che alcuni tocchi, è quello di eseguire il comando in background. In altre parole:

while sleep 1; do (./foo.sh &) ; done

E questo è tutto quello che c'è da fare.

È possibile eseguirlo ogni 500 ms con sleep 0.5 o cosa hai.

33
swalog

In bash:

bash -c 'while [ 0 ]; do echo "I will not automate this punishment in absurdum."; done'

(echo potrebbe essere sostituito da qualsiasi comando ...

O in Perl:

Perl -e 'for (;1;) {print "I will not automate this punishment in absurdum.\n"}'

Dove print "I will not automate this punishment in absurdum.\n" potrebbe essere sostituito con il comando "any" circondato da backtick (`).

E per una pausa, aggiungi un'istruzione sleep all'interno del ciclo for:

bash -c 'while [ 0 ]; do echo "I will not automate this punishment in absurdum."; sleep 1; done'

e

Perl -e 'for (;1;) {print "I will not automate this punishment in absurdum.\n"; sleep 1}'
17
Tooony

Bash recente> = 4.2 con kernel Linux recente, risposta basata.

Per limitare i tempi di esecuzione, non ci sono nessuna forcella ! Vengono utilizzati solo i built-in.

Per questo, uso read funzione integrata invece di sleep. Purtroppo questo non funzionerà con le sessioni notty.

Veloce bash funzione "repeat" come richiesto:

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
}

Piccolo test con stringhe tra virgolette:

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

A seconda della granularità e della durata del comando inviato ...

Sotto i recenti kernel Linux, c'è un procfile /proc/timer_list contenente informazioni temporali in nanosecondi.

Se si desidera eseguire un comando esattamente una volta al secondo, il comando deve terminare in meno di un secondo! E da lì, devi sleep solo il resto del secondo attuale.

Se il ritardo è più importante e il comando non richiede tempo significativo, è possibile:

command=(echo 'Hello world.')
delay=10
while :;do
    printf -v now "%(%s)T" -1
    read -t $(( delay-(now%delay) )) foo
    ${command[@]}
  done.

Ma se il tuo obiettivo è ottenere una granularità più fine, devi:

Usa le informazioni sui nanosecondi per attendere l'inizio di un secondo ...

Per questo, ho scritto una piccola funzione 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
}

Dopo averli acquistati, potresti:

command=(echo 'Hello world.')
while :;do
    waitNextSecondHires
    ${command[@]}
  done.

correre ${command[@]} direttamente dalla riga di comando, rispetto a

command=(eval "echo 'Hello world.';sleep .3")
while :;do
    waitNextSecondHires
    ${command[@]}
  done.

questo deve dare esattamente lo stesso risultato.

Assunzioni bash funzione "repeat" come richiesto:

Potresti procurartelo:

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
}

Quindi provalo:

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
10
F. Hauri

Bash e alcune delle sue conchiglie affini hanno il comodo (( ... )) notazione in cui è possibile valutare espressioni aritmetiche.

Quindi, come risposta alla tua terza sfida, in cui sia il conteggio delle ripetizioni che il ritardo tra ciascuna ripetizione dovrebbero essere configurabili, ecco un modo per farlo:

repeat=10
delay=1

i=0
while (( i++ < repeat )); do
  echo Repetition $i
  sleep $delay
done

Questa risposta soffre anche della deriva temporale descritta nella risposta di Keith .

4
Thor

Se la tua intenzione non è quella di visualizzare un messaggio sullo schermo e se puoi permetterti di ripetere il lavoro in termini di minuti, crontab , forse, sarebbe il tuo strumento migliore. Ad esempio, se desideri eseguire il tuo comando ogni minuto, dovresti scrivere qualcosa del genere nel tuo file crontab:

* * * * * my_precious_command

Consulta il tutorial per ulteriori esempi. Inoltre, puoi impostare facilmente i tempi usando Crontab Code Generator .

4
Barun

Perl

#!/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); 
}
4
dreeves

Ti interessa provare questo (Bash)?

forever ()   {
    TIMES=shift;
    SLEEP=shift;
    if [ "$TIMES" = "-1" ]; then  
        while true;
        do 
            [email protected]
            sleep $SLEEP
        done
    else
        repeat "$TIMES" [email protected] 
    fi; }
4
Gregg Lind

Come menzionato da gbrandt, se il comando watch è disponibile, utilizzalo sicuramente. Alcuni sistemi Unix, tuttavia, non lo hanno installato per impostazione predefinita (almeno non funzionano dove lavoro).

Ecco un'altra soluzione con sintassi e output leggermente diversi (funziona in BASH e SH):

while [ 1 ] ; do
    <cmd>
    sleep <x>
    echo ">>>>>>>>>>>>>" `date` ">>>>>>>>>>>>>>"
done

Modifica: ho rimosso alcuni "." nell'ultima dichiarazione di eco ... holdover dai miei giorni Perl;)

3
bedwyr

E se avessimo entrambi?

Ecco l'idea: con solo "intervallo", si ripete per sempre. Con "intervallo" e "volte", ripete questo numero di volte, separato da "intervallo".

L'utilizzo :

$ loop [interval [times]] command

Quindi, l'algoritmo sarà:

  • Voce di elenco
  • se $ 1 contiene solo cifre, è l'intervallo (predefinito 2)
  • se $ 2 contiene solo cifre, è il numero di volte (infinito predefinito)
  • while loop con questi parametri
    • "intervallo" di sonno
    • se è stato dato un numero di volte, decrementa un var fino a quando non viene raggiunto

Perciò :

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]
        [email protected]
    done
}
3
Baronsed

Ho finito per creare una variante sulla risposta del cagnolino. Con i suoi hai dovuto aspettare X secondi per la prima iterazione, sto anche eseguendo il mio in primo piano quindi ..

./foo.sh;while sleep 1; do (./foo.sh) ; done
2
Duane Lortie

È possibile ottenere il potere da python da Shell.

python3 -c "import time, os
while True:
    os.system('your command')
    time.sleep(5)

sostituisci cinque in qualsiasi momento (sec) che ti piace.

Attenzione: il ritorno usa MAIUSC + INVIO per restituire input in Shell. E non dimenticare di digitare 4 SPAZIO rientro in ciascuna riga del ciclo while.

1
pah8J

Modo semplice per ripetere un lavoro da crontab con un intervallo inferiore a un minuto (esempio 20 secondi):

crontab: * * * * * script.sh

script.sh:

#!/bin/bash
>>type your commands here.

sleep 20
>>retype your commands here.

sleep 20
>>retype your commands here.
1
Charles nakhel

È possibile eseguire uno script da init (aggiungendo una riga a/etc/inittab). Questo script deve eseguire il comando, dormire per il tempo che si desidera attendere fino a quando non si esegue nuovamente lo script ed uscire. Init riavvierà nuovamente lo script dopo l'uscita.

1
Roberto Paz

Veloce, sporco e probabilmente pericoloso da avviare, ma se sei avventuroso e sai cosa stai facendo, mettilo in repeat.sh e chmod 755 it,

while true
do 
    eval $1 
    sleep $2 
done

Richiamalo con ./repeat.sh <command> <interval>

Il mio senso spidey dice che questo è probabilmente un modo malvagio per farlo, il mio senso spidey è giusto?

1
mallyone
#! /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
0
user56318

... Mi chiedo quante soluzioni complicate possano essere create per risolvere questo problema.

Può essere così facile ...

open /etc/crontab 

metti 1 riga successiva alla fine del file come:

*/NumberOfSeconds * * * * user /path/to/file.sh

Se vuoi eseguire qualcosa ogni 1 secondo, inserisci lì:

*/60 * * * * root /path/to/file.sh 

where that file.sh could be chmod 750 /path/to/file.sh

e all'interno di quel file.sh dovrebbe essere:

#!/bin/bash 
#What does it do
#What is it runned by

your code or commands

e questo è tutto!

GODERE!

0
MIrra

Con zsh e un'implementazione sleep che accetta argomenti in virgola mobile:

typeset -F SECONDS=0 n=0
repeat 100 {cmd; sleep $(((n+=3) - SECONDS))}

O per forever:

for ((;;)) {cmd; sleep $(((n+=3) - SECONDS))}

Se sleep non supporta i float, puoi sempre ridefinirlo come un wrapper intorno al zsh's zselect builtin:

zmodload zsh/zselect
sleep() zselect -t $((($1 * 100) | 0))
0

Potresti procurarti questa funzione ricorsiva:

#!/bin/bash
ininterval () {
    delay=$1
    shift
    $*
    sleep $delay
    ininterval $delay $*
}

o aggiungi un:

ininterval $*

e chiama la sceneggiatura.

0
user unknown

Per eseguire ripetutamente un comando in una finestra della console di solito eseguo qualcosa del genere:

while true; do (run command here); done

Funziona anche con più comandi, ad esempio per visualizzare un orologio in continuo aggiornamento in una finestra della console:

while true; do clear; date; sleep 1; done

0
Thomas Bratt

Questa soluzione funziona in MacOSX 10.7. Funziona meravigliosamente.

bash -c 'while [ 0 ]; do \
      echo "I will not automate this punishment in absurdum."; done'

Nel mio caso

bash -c 'while [ 0 ]; do ls; done'

o

bash -c 'while [ 0 ]; do mv "Desktop/* Documents/Cleanup"; done'

per pulire costantemente il mio desktop.

0
Dan Ruiz