desenv-web-rp.com

Repita um comando Unix a cada x segundos para sempre

Existe um comando interno do 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."

ecoará a sequência especificada 100 vezes e depois parará.

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

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

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

PS: Talvez uma maneira melhor de fazer isso seja generalizar repeat para levar o número de vezes a repetir (com -1 significando infinito) e o número de segundos para dormir entre 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."
497
dreeves

Experimente o comando watch.

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

De modo a:

watch -n1  command

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

Deseja passar o comando para exec em vez de sh -c, usar -x opção:

watch -n1 -x command

No macOS, você pode obter watch de portas do Mac :

port install watch

Ou você pode obtê-lo em Homebrew :

brew install watch
637
Gregor Brandt

Bash

while + sleep:

while true
do 
    echo "Hi"
    sleep 1
done

Aqui está a mesma coisa que uma linha de taquigrafia (Dos comentários abaixo):

while sleep 1; do echo "Hi"; done

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

255
pope

Esta é apenas uma versão mais curta de outros while+sleep respostas, se você estiver executando esse tipo de tarefa frequentemente como sua rotina diária, isso evita que você pressione desnecessariamente as teclas e se sua linha de comando começa a entender mais, é mais fácil. Mas este começa com o sono primeiro.

Isso geralmente é útil se você precisar seguir algo com saída em uma linha, como carga da máquina:

while sleep 1; do uptime; done
73
Bekir Dogan

Um problema que todas as respostas postadas até agora têm é que o tempo em que o comando é executado pode ser desviado. Por exemplo, se você fizer um sleep 10 entre comandos, e o comando leva 2 segundos para ser executado; depois, ele é executado a cada 12 segundos; se leva um tempo variável para ser executado, a longo prazo, o tempo em que é executado pode ser imprevisível.

Isso pode ser exatamente o que você deseja; Nesse caso, use uma das outras soluções ou use essa, mas simplifique a chamada sleep.

Para resolução de um minuto, os trabalhos cron serão executados no horário especificado, independentemente do tempo que cada comando leve. (De fato, um novo trabalho cron será iniciado, mesmo que o anterior ainda esteja em execução.)

Aqui está um script Perl simples que dorme até o próximo intervalo. 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 se o O comando em si leva alguns segundos. Se o comando executar mais de interval segundos, o próximo intervalo será ignorado (diferente 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?)
}
46
Keith Thompson

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

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

A verdadeira questão era:

  • "Como executar um programa a cada X segundos"

Essas são duas coisas muito diferentes quando o comando leva algum tempo para terminar.

Tomemos, 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 sugere 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 é exatamente executado a cada segundo. Mesmo com o sinalizador -p, Como sugere a página man watch, Isso pode resolver o problema, o resultado é o mesmo.

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

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

E isso é tudo o que existe.

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

33
swalog

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 cercado por reticulares (`).

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}'
17
Tooony

Recent bash> = 4.2 no kernel recente do Linux, com base em respostas.

Para limitar o tempo de execução, não há nenhum garfo ! Somente interno é usado.

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

Rápida bash função "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 seqüências de caracteres 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 ...

Nos kernels recentes do Linux, existe um procfile /proc/timer_list contendo informações de tempo em nanossegundos.

Se você deseja executar um comando exatamente uma vez por segundo, seu comando deve terminar em menos de um segundo! E a partir daí, você deve sleep apenas o resto do segundo atual.

Se o atraso for mais importante e seu comando não exigir um 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 fina, você deve:

Use as informações em nanossegundos para esperar até o início 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 adquiri-los, você pode:

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

corre ${command[@]} diretamente na linha de comando, que comparar com

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

isso deve dar exatamente o mesmo resultado.

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

Você pode obter 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
10
F. Hauri

O Bash e algumas de suas conchas semelhantes possuem a notação conveniente (( ... )), Na qual expressões aritméticas podem ser avaliadas.

Portanto, como resposta ao seu terceiro desafio, em que a contagem de repetições e o atraso entre cada repetição devem ser configuráveis, aqui está uma maneira de fazer isso:

repeat=10
delay=1

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

Essa resposta também sofre com o desvio de tempo coberto em Keith resposta .

4
Thor

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

* * * * * my_precious_command

Confira o tutorial para mais exemplos. Além disso, você pode definir os horários facilmente 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

Gostaria de experimentar isso (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

Conforme mencionado por gbrandt, se o comando watch estiver disponível, use-o definitivamente. Alguns sistemas Unix, no entanto, não o instalam por padrão (pelo menos não 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: removi alguns "." na última declaração de eco ... ressaca dos meus dias Perl;)

3
bedwyr

E se tivéssemos os dois?

Aqui está a idéia: com apenas "intervalo", ele se repete para sempre. Com "intervalo" e "tempos", 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, será o intervalo (padrão 2)
  • se $ 2 contiver apenas dígitos, é o número de vezes (infinito padrão)
  • loop while com esses parâmetros
    • intervalo de sono
    • se um número de vezes foi dado, diminua um var até que seja atingido

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

Acabei criando uma variante da resposta do swalog. Com o dele, você teve que esperar X segundos pela primeira iteração, também estou executando o meu em primeiro plano, então ..

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

Você pode obter o poder do python do Shell.

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

substitua cinco a qualquer momento (s) que desejar.

Atenção: o retorno use SHIFT + ENTER para retornar a entrada no Shell. E não esqueça de digitar 4 SPACE recuar em cada linha no loop while.

1
pah8J

Maneira fácil de repetir um trabalho do crontab com um intervalo inferior a 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.
1
Charles nakhel

Você pode executar um script a partir do init (adicionando uma linha ao/etc/inittab). Esse 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.

1
Roberto Paz

Rápida, suja e provavelmente perigosa para inicializar, mas se você é aventureiro e sabe 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 aranha diz que esta é provavelmente uma maneira maligna de fazer isso, meu senso de aranha está certo?

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

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

Pode ser tão fácil ...

open /etc/crontab 

coloque lá 1 linha ao final do arquivo como:

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

Se você deseja 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 estar:

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

your code or commands

e isso é tudo!

DESFRUTAR!

0
MIrra

Com a implementação 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 suporta flutuações, você sempre pode redefini-lo como um wrapper em torno do zsh 's zselect embutido:

zmodload zsh/zselect
sleep() zselect -t $((($1 * 100) | 0))
0
Stéphane Chazelas

Você pode obter esta função recursiva:

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

ou adicione um:

ininterval $*

e chame o script.

0
user unknown

Para executar repetidamente um comando em uma janela do console, normalmente executo algo como isto:

while true; do (run command here); done

Isso funciona para vários comandos, por exemplo, para exibir um relógio de atualização contínua em uma janela do console:

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

0
Thomas Bratt

Esta solução funciona no MacOSX 10.7. 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 minha área de trabalho constantemente.

0
Dan Ruiz