Repete um comando Unix a cada x segundo para sempre

433

Existe um comando interno Unix repeat cujo primeiro argumento é o número de vezes para repetir um comando, onde o comando (com qualquer argumento) é especificado pelos argumentos restantes para repeat .

Por exemplo,

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

vai ecoar a string dada 100 vezes e depois parar.

Eu gostaria de um comando similar - vamos chamá-lo de forever - que funciona da mesma forma, exceto que o primeiro argumento é o número de segundos para fazer uma pausa entre as repetições, e ele se repete para sempre. Por exemplo,

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

Eu pensei em perguntar se existe algo assim antes de escrevê-lo. Eu sei que é como um script Perl ou Python de 2 linhas, mas talvez haja uma maneira mais padrão de fazer isso. Se não estiver, sinta-se à vontade para postar uma solução em sua linguagem de script favorita, estilo do Rosetta Stone .

PS: Talvez uma maneira melhor de fazer isso seja generalizar repeat para obter o número de vezes para repetir (com -1 significando infinito) e o número de segundos para dormir entre as repetições. Os exemplos acima se tornariam:

% repeat 100 0 echo "I will not automate this punishment."
% repeat -1 5 echo "This will get echoed every 5 seconds forever."
    
por dreeves 17.02.2009 / 01:11

25 respostas

555

Experimente o comando watch .

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

Para que:

watch -n1  command

executará o comando a cada segundo (bem, tecnicamente, a cada segundo mais o tempo que leva para que command seja executado, pois watch (pelo menos as implementações procps e busybox ) dorme apenas um segundo em entre duas execuções de command ), para sempre.

Você deseja passar o comando para exec em vez de sh -c , use -x option:

watch -n1 -x command

No macOS, você pode obter watch de Mac Ports :

port install watch

Ou você pode obtê-lo em Homebrew :

brew install watch
    
por 17.02.2009 / 01:22
209

Bash

while + sleep :

while true
do 
    echo "Hi"
    sleep 1
done

Aqui está a mesma coisa que um one-liner de taquigrafia (dos comentários abaixo):

while sleep 1; do echo "Hi"; done

Usa ; para separar comandos e usa sleep 1 para o while test, pois sempre retorna true. Você pode colocar mais comandos no loop - basta separá-los com ;

    
por 17.02.2009 / 01:13
58

Esta é apenas uma versão mais curta de outras respostas de while+sleep , se você estiver executando esse tipo de tarefas frequentemente como rotina diária, usar isso evita pressionamentos de tecla desnecessários e se a linha de comando começar a entender mais um é um pouco mais fácil. Mas este começa com dormir primeiro.

Isso geralmente é útil se você precisa seguir algo que tem uma saída de uma linha como a carga da máquina:

while sleep 1; do uptime; done
    
por 19.01.2013 / 16:53
42

Um problema que todas as respostas postadas até agora têm é que o tempo que o comando é executado pode se desviar. Por exemplo, se você executar um sleep 10 entre os comandos e o comando demorar 2 segundos para ser executado, ele será executado a cada 12 segundos; Se demorar um tempo variável para ser executado, a longo prazo, o tempo de execução pode ser imprevisível.

Isso pode ser exatamente o que você quer; Em caso afirmativo, use uma das outras soluções ou use esta, mas simplifique a chamada sleep .

Para resolução de um minuto, os trabalhos agendados serão executados no horário especificado, independentemente do tempo de duração de cada comando. (Na verdade, um novo trabalho do cron será iniciado, mesmo se o anterior ainda estiver em execução.)

Aqui está um script Perl simples que dorme até o próximo intervalo, portanto, por exemplo, com um intervalo de 10 segundos, o comando pode ser executado às 12:34:00, 12:34:10, 12:34:20, etc. mesmo que o comando demore vários segundos. Se o comando for executado em mais de interval segundos, o próximo intervalo será ignorado (ao contrário do cron). O intervalo é calculado em relação à época, portanto, um intervalo de 86400 segundos (1 dia) será executado à meia-noite 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?)
}
    
por 12.01.2012 / 12:29
25

Acho que todas as respostas até aqui são ou muito complicadas ou, em vez disso, respondem a uma pergunta diferente:

  • "Como rodar um programa repetidamente para que haja X segundos de atraso entre o término do programa e o próximo inicie" .

A verdadeira questão era:

  • "Como executar um programa a cada X segundos"

Estas são duas coisas muito diferentes quando o comando demora para terminar.

Pegue, por exemplo, o script foo.sh (finja que este é um programa que leva alguns segundos para ser concluído).

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

Você deseja executar isso a cada segundo e a maioria sugeriria watch -n1 ./foo.sh ou while sleep 1; do ./foo.sh; done . No entanto, isso fornece a saída:

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

O que não está sendo executado exatamente a cada segundo. Mesmo com o -p flag, como a página man watch sugere pode resolver isso, o resultado é o mesmo.

Uma maneira fácil de realizar a tarefa desejada, que alguns abordam, é executar o comando em segundo plano. Em outras palavras:

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

E isso é tudo que existe para isso.

Você pode executá-lo a cada 500 ms com sleep 0.5 ou o que você tem.

    
por 29.01.2014 / 15:42
17

Em bash :

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

( echo pode ser substituído por qualquer comando ...

Ou em perl :

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

Onde print "I will not automate this punishment in absurdum.\n" pode ser substituído por "qualquer" comando rodeado de backticks (').

E para uma pausa, adicione uma instrução sleep dentro do loop 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}'
    
por 17.02.2009 / 01:36
8

Recente bash > = 4.2 sob recente resposta baseada no kernel Linux.

Para limitar o tempo de execução, não há sem garfos ! Somente built-in é usado.

Para isso, uso a função read builtin em vez de sleep . Infelizmente isso não funcionará com as sessões notty .

A funciona " repeat " conforme solicitado:

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
}

Pouco teste com strings citadas:

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

Dependendo da granularidade e duração do comando enviado ...

Sob os kernels recentes do Linux, há um procfile /proc/timer_list contendo informações de tempo em nanossegundos.

Se você quiser executar um comando exatamente uma vez por segundo, seu comando deve terminar em menos de um segundo! E de lá, você precisa sleep somente o restante do segundo atual.

Se o atraso for mais importante e seu comando não exigir tempo significativo, você poderá:

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

Mas se seu objetivo é obter granularidade mais precisa, você precisa:

Use informação de nanossegundos para esperar até o começo de um segundo ...

Para isso, escrevi uma pequena função 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
}

Depois de fazer o sourcing, você pode:

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

execute ${command[@]} diretamente na linha de comando, em comparação a

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

isso deve dar exatamente o mesmo resultado.

Contrata a função " repeat " conforme solicitado:

Você pode pesquisar isso:

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
}

Então tente:

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
    
por 29.01.2014 / 18:31
4

Cuidado para tentar isso (Bash)?

forever ()   {
    TIMES=shift;
    SLEEP=shift;
    if [ "$TIMES" = "-1" ]; then  
        while true;
        do 
            $@
            sleep $SLEEP
        done
    else
        repeat "$TIMES" $@ 
    fi; }
    
por 17.02.2009 / 01:39
4

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); 
}
    
por 17.02.2009 / 01:17
4

Bash e alguns de seus shells afins têm a conveniente (( ... )) de notação em que expressões aritméticas podem ser avaliadas.

Portanto, como resposta ao seu terceiro desafio, onde tanto a contagem de repetição quanto o atraso entre cada repetição devem ser configuráveis, aqui está uma maneira de fazê-lo:

repeat=10
delay=1

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

Esta resposta também sofre com o desvio de tempo coberto pela resposta de Keith .

    
por 26.04.2013 / 12:55
3

Como mencionado por gbrandt, se o comando watch estiver disponível, definitivamente use-o. Alguns sistemas Unix, no entanto, não têm instalado por padrão (pelo menos não funcionam onde eu trabalho).

Aqui está outra solução com sintaxe e saída ligeiramente diferentes (funciona em BASH e SH):

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

Editar: eu removi alguns "." na última declaração de eco ... resquício dos meus dias de Perl;)

    
por 17.02.2009 / 01:39
3

Se sua intenção não é exibir uma mensagem na sua tela, e se você pudesse se dar ao luxo de repetir o trabalho em termos de minutos, crontab , talvez, seja sua melhor ferramenta. Por exemplo, se você deseja executar seu comando a cada minuto, você escreveria algo assim em seu arquivo crontab :

* * * * * my_precious_command

Por favor, confira o tutorial para mais exemplos. Além disso, você pode definir os horários facilmente usando o Crontab Code Generator .

    
por 29.02.2012 / 10:54
3

E se tivéssemos as duas coisas?

Aqui está a ideia: com apenas "intervalo", repete-se para sempre. Com "intervalo" e "tempos", ele repete esse número de vezes, separado por "intervalo".

O uso:

$ loop [interval [times]] command

Então, o algoritmo será:

  • Item da lista
  • se $ 1 contiver apenas dígitos, é o intervalo (padrão 2)
  • se $ 2 contiver apenas dígitos, é o número de vezes (padrão infinito)
  • while loop com esses parâmetros
    • sleep "interval"
    • se um número de vezes foi dado, decrementar um var até que seja alcançado

Portanto:

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
}
    
por 11.11.2013 / 23:27
2

Acabei criando uma variante na resposta da swalog. Com o seu você teve que esperar X segundos para a primeira iteração, eu também estou correndo o meu em primeiro plano, então ..

./foo.sh;while sleep 1; do (./foo.sh) ; done
    
por 13.12.2014 / 08:44
1

Rápido, sujo e provavelmente perigoso, mas se você for aventureiro e souber o que está fazendo, coloque isso em repeat.sh e chmod 755 ,

while true
do 
    eval $1 
    sleep $2 
done

Invoque-o com ./repeat.sh <command> <interval>

Meu senso de liberdade diz que esta é provavelmente uma maneira maléfica de fazer isso, é o meu senso de lado direito?

    
por 28.02.2012 / 19:27
1

Você pode executar um script do init (adicionando uma linha ao / etc / inittab). Este script deve executar seu comando, dormir pelo tempo que você deseja esperar até executar o script novamente e sair dele. O Init iniciará seu script novamente após a saída.

    
por 11.11.2013 / 20:01
1

Modo fácil de repetir um trabalho de crontab com um intervalo menor que um minuto (exemplo de 20 segundos):

crontab: * * * * * script.sh

script.sh:

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

sleep 20
>>retype your commands here.

sleep 20
>>retype your commands here.
    
por 28.03.2014 / 22:26
1

Você pode obter o poder do interpretador python do shell.

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

substitua cinco em qualquer momento (seg) que você quiser.

Atenção: o retorno usa SHIFT + ENTER para retornar a entrada no shell. E não se esqueça de digitar o recuo SPACE em cada linha no loop while.

    
por 19.06.2018 / 15:14
0

Esta solução funciona no MacOSX 10.7. Isso funciona maravilhosamente.

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

No meu caso

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

ou

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

para limpar meu desktop constantemente.

    
por 11.01.2012 / 16:27
0

Você pode criar essa função recursiva:

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

ou adicione um:

ininterval $*

e chame o script.

    
por 05.04.2011 / 14:10
0

Para executar repetidamente um comando em uma janela do console, geralmente executo algo assim:

while true; do (run command here); done

Isso também funciona para vários comandos, por exemplo, para exibir um relógio continuamente atualizado em uma janela do console:

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

    
por 24.12.2012 / 13:09
0

... Gostaria de saber quantas soluções complicadas podem ser criadas ao resolver esse problema.

Pode ser tão fácil ...

open /etc/crontab 

coloque 1 próxima linha no final do arquivo como:

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

Se você quiser executar algo a cada 1 segundo, basta colocar lá:

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

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

e dentro desse arquivo.sh deve ser:

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

your code or commands

e é tudo!

APROVEITE!

    
por 28.12.2012 / 15:28
0
#! /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
    
por 09.01.2014 / 15:32
0

Com a implementação de zsh e sleep que aceita argumentos de ponto flutuante:

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

Ou para forever :

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

Se o seu sleep não suportar floats, você poderá redefini-lo como um wrapper em torno de zsh ' zselect builtin:

zmodload zsh/zselect
sleep() zselect -t $((($1 * 100) | 0))
    
por 26.06.2018 / 13:34
-1
until ! sleep 60; do echo $(date); command; command; command; done

funciona para mim.

  1. Eu não preciso exatamente a cada 60 segundos
  2. "watch" não assiste a comandos em todos os sistemas
  3. "watch" pode receber apenas um comando (ou um arquivo de script)
  4. colocar o sono na condição em vez do corpo torna o loop melhor interrompível (portanto, uma afirmação em uma resposta a uma pergunta semelhante no stackoverflow. infelizmente eu posso encontrá-lo no momento)
  5. Eu quero executá-lo uma vez antes do atraso, então eu uso até em vez de
por 11.06.2016 / 14:59