Executando um cron a cada 30 segundos

313

Ok, então eu tenho um cron que eu preciso executar a cada 30 segundos.

Aqui está o que eu tenho:

*/30 * * * * /bin/bash -l -c 'cd /srv/last_song/releases/20120308133159 && script/rails runner -e production '\''Song.insert_latest'\'''

É executado, mas está sendo executado a cada 30 minutos ou 30 segundos?

Além disso, tenho lido que o cron pode não ser a melhor ferramenta a ser usada se eu o executar com tanta frequência. Existe outra ferramenta melhor que eu possa usar ou instalar no Ubuntu 11.04 que seja uma opção melhor? Existe uma maneira de corrigir o cron acima?

Matt Elhotiby
fonte
CommaToast, e o que acontece se o seu aplicativo Javascript ou Java cair por algum motivo e sair? Como será reiniciado? :-)
paxdiablo 17/02
12
Adicione um pequeno aplicativo NodeJS, lol. Por que não um pequeno aplicativo c ++? Enquanto estamos nisso, podemos chamá-lo de 'cron' e executá-lo como um serviço.
Andrew
Acabei de descobrir isso enquanto olhava para os perfis de usuário e vi que você estava online há menos de uma hora (apenas para verificar se o acesso ainda está em uso). Existe algum motivo específico para você não aceitar as respostas abaixo?
Fabian N.

Respostas:

731

Você tem */30o especificador de minutos - isso significa cada minuto, mas com uma etapa de 30 (em outras palavras, a cada meia hora). Como cronnão diminui para resoluções de menos de um minuto, você precisará encontrar outra maneira.

Uma possibilidade, embora seja um pouco desagradável (a) , é ter dois trabalhos, um compensado por 30 segundos:

# Need these to run on 30-sec boundaries, keep commands in sync.
* * * * *              /path/to/executable param1 param2
* * * * * ( sleep 30 ; /path/to/executable param1 param2 )

Você verá que adicionei comentários e formatei para garantir que seja fácil mantê-los sincronizados.

Ambos os crontrabalhos são executados a cada minuto, mas o último espera meio minuto antes de executar a "carne" do trabalho /path/to/executable,.

Para outras cronopções (sem base), consulte as outras respostas aqui, principalmente as que mencionam fcrone systemd. Provavelmente, é preferível presumir que seu sistema tenha a capacidade de usá-los (como instalar fcronou fazer uma distribuição comsystemd ).


Se você não quiser usar a solução kludgy, poderá usar uma solução baseada em loop com uma pequena modificação. Você ainda precisará manter o processo em execução de alguma forma, mas, assim que for classificado, o seguinte script deverá funcionar:

#!/bin/env bash

# Debug code to start on minute boundary and to
# gradually increase maximum payload duration to
# see what happens when the payload exceeds 30 seconds.

((maxtime = 20))
while [[ "$(date +%S)" != "00" ]]; do true; done

while true; do
    # Start a background timer BEFORE the payload runs.

    sleep 30 &

    # Execute the payload, some random duration up to the limit.
    # Extra blank line if excess payload.

    ((delay = RANDOM % maxtime + 1))
    ((maxtime += 1))
    echo "$(date) Sleeping for ${delay} seconds (max ${maxtime})."
    [[ ${delay} -gt 30 ]] && echo
    sleep ${delay}

    # Wait for timer to finish before next cycle.

    wait
done

O truque é usar um sleep 30mas para iniciá-lo em segundo plano antes que sua carga útil seja executada. Depois que a carga útil terminar, aguarde o segundo planosleep a conclusão de .

Se a carga útil levar nsegundos (onde n <= 30), a espera após a carga útil será de 30 - nsegundos. Se demorar mais de 30 segundos, o próximo ciclo será adiado até a carga útil terminar, mas não mais.

Você verá que eu tenho um código de depuração para iniciar um limite de um minuto para facilitar a saída inicialmente a seguir. Eu também aumento gradualmente o tempo máximo de carga útil, para que você eventualmente veja a carga útil exceder o tempo de ciclo de 30 segundos (uma linha em branco extra é emitida para que o efeito seja óbvio).

A seguir, é executada uma amostra (onde os ciclos normalmente começam 30 segundos após o ciclo anterior):

Tue May 26 20:56:00 AWST 2020 Sleeping for 9 seconds (max 21).
Tue May 26 20:56:30 AWST 2020 Sleeping for 19 seconds (max 22).
Tue May 26 20:57:00 AWST 2020 Sleeping for 9 seconds (max 23).
Tue May 26 20:57:30 AWST 2020 Sleeping for 7 seconds (max 24).
Tue May 26 20:58:00 AWST 2020 Sleeping for 2 seconds (max 25).
Tue May 26 20:58:30 AWST 2020 Sleeping for 8 seconds (max 26).
Tue May 26 20:59:00 AWST 2020 Sleeping for 20 seconds (max 27).
Tue May 26 20:59:30 AWST 2020 Sleeping for 25 seconds (max 28).
Tue May 26 21:00:00 AWST 2020 Sleeping for 5 seconds (max 29).
Tue May 26 21:00:30 AWST 2020 Sleeping for 6 seconds (max 30).
Tue May 26 21:01:00 AWST 2020 Sleeping for 27 seconds (max 31).
Tue May 26 21:01:30 AWST 2020 Sleeping for 25 seconds (max 32).
Tue May 26 21:02:00 AWST 2020 Sleeping for 15 seconds (max 33).
Tue May 26 21:02:30 AWST 2020 Sleeping for 10 seconds (max 34).
Tue May 26 21:03:00 AWST 2020 Sleeping for 5 seconds (max 35).
Tue May 26 21:03:30 AWST 2020 Sleeping for 35 seconds (max 36).

Tue May 26 21:04:05 AWST 2020 Sleeping for 2 seconds (max 37).
Tue May 26 21:04:35 AWST 2020 Sleeping for 20 seconds (max 38).
Tue May 26 21:05:05 AWST 2020 Sleeping for 22 seconds (max 39).
Tue May 26 21:05:35 AWST 2020 Sleeping for 18 seconds (max 40).
Tue May 26 21:06:05 AWST 2020 Sleeping for 33 seconds (max 41).

Tue May 26 21:06:38 AWST 2020 Sleeping for 31 seconds (max 42).

Tue May 26 21:07:09 AWST 2020 Sleeping for 6 seconds (max 43).

Se você deseja evitar a solução kludgy, isso provavelmente é melhor. Você ainda precisará de um crontrabalho (ou equivalente) para detectar periodicamente se esse script está em execução e, se não, iniciá-lo. Mas o próprio script lida com o tempo.


(a) Alguns de meus colegas de trabalho diriam que kludges são minha especialidade :-)

paxdiablo
fonte
29
Esta é uma ótima solução, tanto assim que eu acho que a transcende de kludginess
Ponto de Interrogação
14
@ rubo77, apenas se demorasse menos de um segundo para ser executado :-) Se demorasse 29 segundos, aconteceria às 0:00:00, 0: 00.59, 0:01:00, 0:01:59 e assim por diante .
precisa saber é
1
Super sorrateira, muito criativa!
K Raphael
2
Quais são os colchetes para a segunda linha?
Nigel Alderton
2
Essa é uma solução adorável para um problema que, de outra forma, prejudica a eficácia dos crons para determinadas tarefas que exigem execução em frações de minutos. Obrigado.
Fiddy Bux
67

Você não pode. Cron tem uma granularidade de 60 segundos.

* * * * * cd /srv/last_song/releases/20120308133159 && script/rails runner -e production '\''Song.insert_latest'\''
* * * * * sleep 30 && cd /srv/last_song/releases/20120308133159 && script/rails runner -e production '\''Song.insert_latest'\''
wildplasser
fonte
Essa sintaxe é equivalente à do paxdiablo? Ou existem diferenças sutis?
Nicolas Raoul
A diferença é: usei o caminho original para o binário. O @paxdiablo usou um tipo de meta-sintaxe. (e invoca um sub-shell) #
wildplasser 26/03
1
Eu quis dizer, usando em &&vez de ( ; ).
Nicolas Raoul
2
Desculpe. Não, há uma diferença; o &&operador entra em curto-circuito, portanto, o próximo comando da cadeia não será executado se o anterior falhar.
wildplasser 27/03/18
Essa granularidade não deve ser um problema para uma resolução de menos de um minuto.
Juan Isaza
37

A granularidade de Cron é em minutos e não foi projetada para acordar a cada xsegundo para executar alguma coisa. Execute sua tarefa repetida em um loop e deve fazer o que você precisa:

#!/bin/env bash
while [ true ]; do
 sleep 30
 # do what you need to here
done
Marvin Pinto
fonte
54
Lembre-se de que não é exatamente o mesmo. Se o trabalho demorar 25 segundos (por exemplo), será iniciado a cada 55 segundos, e não a cada 30 segundos. Pode não importar, mas você deve estar ciente das possíveis consequências.
22412
7
Você pode executar o trabalho em segundo plano, e ele será executado em quase exatamente 30 segundos.
precisa
1
enquanto [verdadeiro] durmo 30 # fazer o que você precisa aqui feito --------- feito deve ser em caso pequena
templo
1
Por while [ true ]que você não tem muitas instâncias do mesmo script, já que o cron inicia um novo a cada minuto?
Carcamano
2
Você pode fazer sleep $remainingTimeonde o tempo restante é 30 menos o tempo que o trabalho levou (e limitar em zero se levou> 30 segundos). Assim, dedique um tempo antes e depois do trabalho real e calcule a diferença.
mahemoff 15/09/14
32

Se você estiver executando um sistema operacional Linux recente com SystemD, poderá usar a unidade SystemD Timer para executar seu script em qualquer nível de granularidade que desejar (teoricamente em nanossegundos) e - se desejar - regras de inicialização muito mais flexíveis do que o Cron jamais permitiu. . Não sleepsão necessários kludges

Demora um pouco mais para configurar do que uma única linha em um arquivo cron, mas se você precisar de algo melhor que "Cada minuto", vale a pena o esforço.

O modelo de timer do SystemD é basicamente este: temporizadores são unidades que iniciam as unidades de serviço quando um temporizador termina .

Portanto, para cada script / comando que você deseja agendar, você deve ter uma unidade de serviço e, em seguida, uma unidade de timer adicional. Uma única unidade de timer pode incluir várias agendas; portanto, você normalmente não precisaria de mais de um timer e um serviço.

Aqui está um exemplo simples que registra "Hello World" a cada 10 segundos:

/etc/systemd/system/helloworld.service:

[Unit]
Description=Say Hello
[Service]
ExecStart=/usr/bin/logger -i Hello World

/etc/systemd/system/helloworld.timer:

[Unit]
Description=Say Hello every 10 seconds
[Timer]
OnBootSec=10
OnUnitActiveSec=10
AccuracySec=1ms
[Install]
WantedBy=timers.target

Depois de configurar essas unidades ( /etc/systemd/systemcomo descrito acima, para uma configuração de todo o sistema ou ~/.config/systemd/userpara uma configuração específica do usuário), é necessário ativar o cronômetro (embora não seja o serviço) executando systemctl enable --now helloworld.timer(o --nowsinalizador também inicia o cronômetro imediatamente, caso contrário, ele será iniciado somente após a próxima inicialização ou login do usuário).

Os [Timer]campos de seção usados ​​aqui são os seguintes:

  • OnBootSec - inicie o serviço muitos segundos após cada inicialização.
  • OnUnitActiveSec- inicie o serviço muitos segundos após a última vez que o serviço foi iniciado. É isso que faz com que o timer se repita e se comporte como um trabalho cron.
  • AccuracySec- define a precisão do temporizador. Os cronômetros são tão precisos quanto esse campo é definido, e o padrão é 1 minuto (emula cron). O principal motivo para não exigir a melhor precisão é melhorar o consumo de energia - se o SystemD puder agendar a próxima execução para coincidir com outros eventos, ele precisará ativar a CPU com menos frequência. O 1msexemplo acima não é o ideal - normalmente defino a precisão para 1(1 segundo) em meus trabalhos agendados em menos de um minuto, mas isso significaria que, se você olhar para o log mostrando as mensagens "Olá mundo", verá que geralmente é tarde em 1 segundo. Se você concorda com isso, sugiro definir a precisão para 1 segundo ou mais.

Como você deve ter notado, esse cronômetro não imita muito bem o Cron - no sentido de que o comando não inicia no início de cada período do relógio de parede (ou seja, não inicia no décimo segundo no relógio, depois o dia 20 e assim por diante). Em vez disso, acontece apenas quando o temporizador termina. Se o sistema inicializar às 12:05:37, na próxima vez em que o comando for executado, será às 12:05:47, às 12:05:57, etc. Se você estiver interessado na precisão real do relógio de parede, poderá deseja substituir os OnBootSece OnUnitActiveSeccampos e, em vez definir uma OnCalendarregra com a agenda que você quer (que, tanto quanto eu entendo não pode ser mais rápido do que 1 segundo, utilizando o formato de calendário). O exemplo acima também pode ser escrito como:

OnCalendar=*-*-* *:*:00,10,20,30,40,50

Última nota: como você provavelmente adivinhou, a helloworld.timerunidade inicia a helloworld.serviceunidade porque ela tem o mesmo nome (menos o sufixo do tipo de unidade). Esse é o padrão, mas você pode substituí-lo definindo o Unitcampo para a [Timer]seção.

Detalhes mais sangrentos podem ser encontrados em:

Guss
fonte
2
Muitas vezes encontro respostas melhores como essa na seção de comentários. IMHO, embora cron tem sido grampo para tarefas agendadas, esta resposta deve ser o único aceite, uma vez que não é um "hack trabalho" dos sonos e arriscar paralelização de executar tarefas de longa duração, considerando o intervalo / frequência necessária
Qiqo
2
resposta excelente, deve ser a resposta selecionada
GuidedHacking 21/02
21

Não há necessidade de duas entradas cron, você pode colocá-lo em um com:

* * * * * /bin/bash -l -c "/path/to/executable; sleep 30 ; /path/to/executable"

então no seu caso:

* * * * * /bin/bash -l -c "cd /srv/last_song/releases/20120308133159 && script/rails runner -e production '\''Song.insert_latest'\'' ; sleep 30 ; cd /srv/last_song/releases/20120308133159 && script/rails runner -e production '\''Song.insert_latest'\''"

Andrew
fonte
11
Nota: isto só funciona correta se o script leva menos de um segundo para executar
rubo77
2
Rubo - se o trabalho demorar alguns segundos (em vez de mili ou microssegundos) para ser concluído, você não o executará a cada trinta segundos para poder executar duas vezes por minuto. Então, sim, comece com 30 e subtraia do número aproximado de segundos por execução, se maior que 1 segundo.
Andrew
2
Isso também não ajuda se você deseja receber relatórios de erros para cada execução separadamente.
Joeln 03/07/19
joelin - o comando que eu dou não impede a obtenção de dados de log ou saída, simplifiquei o comando para resolver a questão. Para capturar o log, cada comando pode / deve ter a saída redirecionada se você precisar de log, por exemplo, script / rails runner -e production '\' 'Song.insert_latest' \ '' pode ser escrito como script / rails runner -e production '\' 'Song.insert_latest' \ '' 2> & 1> / path_to_logfile e novamente pode ser feito para cada comando na entrada cron única.
Andrew
13

Você pode conferir minha resposta a essa pergunta semelhante

Basicamente, incluí lá um script bash chamado "runEvery.sh", que você pode executar com cron a cada 1 minuto e passar como argumentos o comando real que você deseja executar e a frequência em segundos em que você deseja executá-lo.

algo assim

* * * * * ~/bin/runEvery.sh 5 myScript.sh

shlomia
fonte
10

Use o relógio:

$ watch --interval .30 script_to_run_every_30_sec.sh
user7079817
fonte
posso usar algo parecido $ watch --interval .10 php some_file.php? ou watchfunciona apenas com arquivos .sh?
Yevhenii Shashkov
Você pode executar qualquer coisa com o relógio. No entanto, o intervalo está entre o final e o início do próximo comando, portanto --interval .30, não será executado duas vezes por minuto. Ou seja watch -n 2 "sleep 1 && date +%s", aumentará a cada 3s.
jmartori
observe que watchfoi desenvolvido para uso do terminal, portanto, embora possa funcionar sem um terminal (execute com o nohuplogout) ou com um terminal falso (como screen), ele não oferece recursos para o comportamento do cron, como recuperação de falhas, reiniciando após a inicialização, etc '.
Guss
9

O trabalho Cron não pode ser usado para agendar um trabalho no intervalo de segundos. Você não pode agendar um trabalho cron para executar a cada 5 segundos. A alternativa é escrever um script de shell que usesleep 5 comando nele.

Crie um script de shell a cada 5 segundos após o uso do bash while loop, como mostrado abaixo.

$ cat every-5-seconds.sh
#!/bin/bash
while true
do
 /home/ramesh/backup.sh
 sleep 5
done

Agora, execute este script de shell em segundo plano usando nohupcomo mostrado abaixo. Isso continuará executando o script mesmo após o logout da sua sessão. Isto executará seu script de shell backup.sh a cada 5 segundos.

$ nohup ./every-5-seconds.sh &
Pankaj Shinde
fonte
4
O tempo estará à deriva. Por exemplo, se backup.shlevar 1,5 segundos para executar, ele será executado a cada 6,5 ​​segundos. Existem maneiras de evitar isso, por exemplosleep $((5 - $(date +%s) % 5))
Keith Thompson
Eu sou novo nohup, ao executar seu exemplo, o nohup está retornando 'nenhum arquivo ou diretório'. Após algumas pesquisas, você parece ter perdido 'sh' após nohup. Como assim: $ nohup sh ./every-5-seconds.sh &
VHanded
6

Use fcron ( http://fcron.free.fr/ ) - fornece granularidade em segundos e é muito melhor e mais rico em recursos que cron (vixie-cron) e também estável. Eu costumava fazer coisas estúpidas, como ter cerca de 60 scripts PHP rodando em uma máquina em ambientes muito estúpidos e ainda assim fazia o seu trabalho!

Adi Chiru
fonte
1
Confissões de um desenvolvedor PHP; )
Eric Kigathi
3
Na verdade, confissões de um engenheiro de sistema permitindo que os desenvolvedores PHP .... :)
Adi Chiru
6

em dir /etc/cron.d/

novo criar um arquivo excute_per_30s

* * * * * yourusername  /bin/date >> /home/yourusername/temp/date.txt
* * * * * yourusername sleep 30; /bin/date >> /home/yourusername/temp/date.txt

irá executar cron a cada 30 segundos

lingyfh
fonte
4

Atualmente, estou usando o método abaixo. Funciona sem problemas.

* * * * * /bin/bash -c ' for i in {1..X}; do YOUR_COMMANDS ; sleep Y ; done '

Se você deseja executar a cada N segundos, X será 60 / N e Y será N .

Obrigado.

sujoshi
fonte
Você provavelmente deseja mudar YOUR_COMMANDSpara YOUR_COMMANDS &, para que o comando seja iniciado em segundo plano, caso contrário, se o comando demorar mais de uma fração de segundo - atrasará o próximo lançamento. Assim, com X = 2 e Y = 30, se o comando demorar 10 segundos - ele será iniciado no minuto e 40 segundos depois, em vez de 30. Kudus para @paxdiablo.
Guss
Por alguma razão, se eu omitir a /bin/bash -cparte (incluindo as aspas do argumento), o script será executado apenas a cada minuto, ignorando a iteração (no meu caso X=12e Y=5).
abiyi
3

O trabalho do Crontab pode ser usado para agendar um trabalho em minutos / horas / dias, mas não em segundos. A alternativa :

Crie um script para executar a cada 30 segundos:

#!/bin/bash
# 30sec.sh

for COUNT in `seq 29` ; do
  cp /application/tmp/* /home/test
  sleep 30
done

Use crontab -ee um crontab para executar este script:

* * * * * /home/test/30sec.sh > /dev/null
szaier
fonte
2
se eu entendi direito, esse script é executado 30 vezes e aguarda 30 segundos entre cada iteração. Como faz sentido executá-lo a cada minuto no cron?
FuzzyAmi
2

Você pode executar esse script como um serviço, reiniciar a cada 30 segundos

Registrar um serviço

sudo vim /etc/systemd/system/YOUR_SERVICE_NAME.service

Cole o comando abaixo

Description=GIVE_YOUR_SERVICE_A_DESCRIPTION

Wants=network.target
After=syslog.target network-online.target

[Service]
Type=simple
ExecStart=YOUR_COMMAND_HERE
Restart=always
RestartSec=10
KillMode=process

[Install]
WantedBy=multi-user.target

Recarregar serviços

sudo systemctl daemon-reload

Ativar o serviço

sudo systemctl enable YOUR_SERVICE_NAME

Iniciar o serviço

sudo systemctl start YOUR_SERVICE_NAME

Verifique o status do seu serviço

systemctl status YOUR_SERVICE_NAME
Nguyen Nguyen
fonte
1

Obrigado por todas as boas respostas. Para simplificar, gostei da solução mista, com o controle no crontab e a divisão do tempo no script. Então, foi isso que fiz para executar um script a cada 20 segundos (três vezes por minuto). Linha Crontab:

 * * * * 1-6 ./a/b/checkAgendaScript >> /home/a/b/cronlogs/checkAgenda.log

Roteiro:

cd /home/a/b/checkAgenda

java -jar checkAgenda.jar
sleep 20
java -jar checkAgenda.jar 
sleep 20
java -jar checkAgenda.jar 
jfajunior
fonte
o que 1-6 representa?
Phantom007
@ Phantom007 1-6 representa de segunda a sábado, onde "-" é um intervalo e "0" é domingo. Aqui está um bom link que explica muito bem todos os campos e onde você pode testá-lo: " crontab.guru/# * _ * _ * _ * _ 1-6"
jfajunior 20/08/19
1

escrever um script de shell criar arquivo .sh

nano every30second.sh

e escrever script

#!/bin/bash
For  (( i=1; i <= 2; i++ ))
do
    write Command here
    sleep 30
done

então configure cron para este script crontab -e

(* * * * * /home/username/every30second.sh)

este cron chama o arquivo .sh a cada 1 min e no comando .sh file é executado 2 vezes em 1 min

se você deseja executar o script por 5 segundos, substitua 30 por 5 e mude para loop assim: For (( i=1; i <= 12; i++ ))

quando você selecionar por qualquer segundo, calcule 60 / seu segundo e escreva no loop For

Aditya Gosavi
fonte
0

Eu apenas tive uma tarefa semelhante a fazer e usar a seguinte abordagem:

nohup watch -n30 "kill -3 NODE_PID" &

Eu precisava ter um kill periódico -3 (para obter o rastreamento de pilha de um programa) a cada 30 segundos por várias horas.

nohup ... & 

Isso está aqui para garantir que eu não perco a execução do relógio se perder o shell (problema de rede, falha do Windows etc.)

Thomas
fonte
0

Dê uma olhada no frequent-cron - é antigo, mas muito estável e você pode passar para microssegundos. Neste momento, a única coisa que eu diria contra isso é que ainda estou tentando descobrir como instalá-lo fora do init.d, mas como um serviço systemd nativo, mas certamente até o Ubuntu 18 está executando apenas bem ainda usando init.d (a distância pode variar nas últimas versões). Ele tem a vantagem adicional (?) De garantir que não gerará outra instância do script PHP, a menos que uma anterior tenha sido concluída, o que reduz possíveis problemas de vazamento de memória.

bnoeafk
fonte
-1

Execute em um loop de shell, exemplo:

#!/bin/sh    
counter=1
while true ; do
 echo $counter
 counter=$((counter+1))
 if [[ "$counter" -eq 60 ]]; then
  counter=0
 fi
 wget -q http://localhost/tool/heartbeat/ -O - > /dev/null 2>&1 &
 sleep 1
done
Lo Vega
fonte
Mesmo assumindo que 60deveria ser um 30, convém movê-lo para wget dentro da ifinstrução, caso contrário, ele é executado a cada segundo. De qualquer forma, não tenho certeza de como isso é melhor do que apenas um sleep 30. Se você estivesse monitorando o tempo real do UNIX e não o seu contador, isso faria diferença.
22418
eco da impressão do contador, você pode descobrir o tempo atrasado por EXCUTE COMMAND se NÃO executar o wget em segundo plano.
Lo Vega