Ejecutando un cron cada 30 segundos

313

Ok, entonces tengo un cron que necesito ejecutar cada 30 segundos.

Esto es lo que tengo:

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

Se ejecuta, pero ¿se está ejecutando cada 30 minutos o 30 segundos?

Además, he estado leyendo que cron podría no ser la mejor herramienta para usar si lo ejecuto con tanta frecuencia. ¿Hay otra herramienta mejor que pueda usar o instalar en Ubuntu 11.04 que será una mejor opción? ¿Hay alguna manera de arreglar el cron anterior?

Matt Elhotiby
fuente
CommaToast, ¿y qué sucede si su aplicación Javascript o Java se cae por alguna razón y se cierra? ¿Cómo se reiniciará? :-)
paxdiablo
12
Agregue una pequeña aplicación NodeJS, jajaja. ¿Por qué no una pequeña aplicación de C ++? Mientras estamos en eso, podemos nombrarlo 'cron' y ejecutarlo como un servicio.
Andrew
Acabo de encontrar esto mientras miraba los perfiles de usuario y vi que estaba en línea hace menos de 1 hora (solo para verificar si el acc todavía está en uso), ¿hay alguna razón específica por la que no aceptó ninguna de las respuestas a continuación?
Fabian N.

Respuestas:

731

Tienes */30el especificador de minutos , eso significa cada minuto pero con un paso de 30 (en otras palabras, cada media hora). Como cronno se reduce a resoluciones de menos de un minuto, deberá buscar otra forma.

Una posibilidad, aunque es un poco un error (a) , es tener dos trabajos, uno 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 )

Verá que agregué comentarios y los formateé para garantizar que sea fácil mantenerlos sincronizados.

Ambos crontrabajos realmente se ejecutan cada minuto, pero el último esperará medio minuto antes de ejecutar la "carne" del trabajo /path/to/executable.

Para otras cronopciones (no basadas), vea las otras respuestas aquí, particularmente las que mencionan fcrony systemd. Probablemente sean preferibles suponiendo que su sistema tenga la capacidad de usarlos (como instalar fcrono tener una distribución systemddentro).


Si no desea usar la solución kludgy, puede usar una solución basada en bucles con una pequeña modificación. Todavía tendrá que administrar mantener su proceso ejecutándose de alguna forma, pero, una vez que esté ordenado, el siguiente script debería 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

El truco consiste en utilizar un sleep 30pero para iniciarlo en segundo plano antes de que se ejecute su carga útil. Luego, una vez finalizada la carga útil, solo espere sleepa que termine el fondo .

Si la carga útil tarda nsegundos (donde n <= 30), la espera después de la carga útil será 30 - nsegundos. Si demora más de 30 segundos, el siguiente ciclo se retrasará hasta que finalice la carga útil, pero ya no.

Verá que tengo un código de depuración para comenzar en un límite de un minuto para hacer que la salida sea inicialmente más fácil de seguir. También aumento gradualmente el tiempo máximo de carga útil para que eventualmente vea que la carga útil excede el tiempo de ciclo de 30 segundos (se genera una línea en blanco adicional para que el efecto sea obvio).

Sigue una ejecución de muestra (donde los ciclos normalmente comienzan 30 segundos después del 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).

Si desea evitar la solución kludgy, probablemente sea mejor. Aún necesitará un crontrabajo (o equivalente) para detectar periódicamente si este script se está ejecutando y, si no, iniciarlo. Pero el guión mismo maneja el tiempo.


(a) Algunos de mis compañeros de trabajo dirían que los kludges son mi especialidad :-)

paxdiablo
fuente
29
Esta es una gran solución, tanto que creo que trasciende su falta de claridad
Interrogación
14
@ rubo77, solo si tardó menos de un segundo en ejecutarse :-) Si tardara 29 segundos, sucedería a las 0:00:00, 0: 00.59, 0:01:00, 0:01:59 y así sucesivamente .
paxdiablo
1
Súper astuto, muy creativo!
K Raphael
2
¿Cuáles son los corchetes para la segunda línea?
Nigel Alderton
2
Esta es una solución encantadora para un problema que de otro modo paraliza la efectividad de los crons para ciertas tareas que requieren ejecución en fracciones de minutos. Gracias.
Fiddy Bux
67

No puedes Cron tiene una granularidad 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
fuente
¿Es esta sintaxis equivalente a la de paxdiablo? ¿O hay diferencias sutiles?
Nicolas Raoul
La diferencia es: utilicé la ruta original al binario. @paxdiablo usó una especie de meta-sintaxis. (e invoca un sub-shell)
wildplasser
1
Quise decir, usando en &&lugar de ( ; ).
Nicolas Raoul
2
Lo siento. No, hay una diferencia; el &&operador realiza un cortocircuito, por lo que el siguiente comando en la cadena no se ejecuta si el anterior falla.
wildplasser
Esta granularidad no debería ser un problema para una resolución de menos de un minuto.
juan Isaza
37

La granularidad de Cron está en minutos y no fue diseñada para despertarse cada xsegundo para ejecutar algo. Ejecute su tarea repetitiva dentro de un bucle y debería hacer lo que necesita:

#!/bin/env bash
while [ true ]; do
 sleep 30
 # do what you need to here
done
Marvin Pinto
fuente
54
Tenga en cuenta que no es absolutamente la misma. Si el trabajo tarda 25 segundos (por ejemplo), comenzará cada 55 segundos en lugar de cada 30 segundos. Puede que no importe, pero debe tener en cuenta las posibles consecuencias.
paxdiablo
77
Puede ejecutar el trabajo en segundo plano, luego se ejecutará en casi exactamente 30 segundos.
Chris Koston
1
mientras que [verdad] duerme 30 # haz lo que necesites hacer aquí --------- hecho debería ser en minúsculas
templo
1
¿No while [ true ]te hará tener muchas instancias del mismo script, ya que cron comenzará una nueva cada minuto?
Carcamano
2
Puede hacerlo sleep $remainingTimedonde el tiempo restante sea 30 menos el tiempo que tomó el trabajo (y limitarlo a cero si tardó> 30 segundos). Entonces se toma el tiempo antes y después del trabajo real, y calcula la diferencia.
mahemoff
32

Si está ejecutando un sistema operativo Linux reciente con SystemD, puede usar la unidad de temporizador SystemD para ejecutar su secuencia de comandos en cualquier nivel de granularidad que desee (teóricamente hasta nanosegundos) y, si lo desea, reglas de inicio mucho más flexibles de lo que Cron alguna vez permitió . No se sleeprequieren errores

Se necesita un poco más de configuración que una sola línea en un archivo cron, pero si necesita algo mejor que "Cada minuto", vale la pena el esfuerzo.

El modelo de temporizador SystemD es básicamente esto: los temporizadores son unidades que inician unidades de servicio cuando transcurre un temporizador .

Entonces, para cada script / comando que desee programar, debe tener una unidad de servicio y luego una unidad de temporizador adicional. Una sola unidad de temporizador puede incluir múltiples horarios, por lo que normalmente no necesitaría más de un temporizador y un servicio.

Aquí hay un ejemplo simple que registra "Hello World" 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

Después de configurar estas unidades (en /etc/systemd/system, como se describió anteriormente, para una configuración de todo el sistema o ~/.config/systemd/userpara una configuración específica del usuario), debe habilitar el temporizador (no el servicio) ejecutando systemctl enable --now helloworld.timer(el --nowindicador también inicia el temporizador inmediatamente, de lo contrario, solo comenzará después del siguiente arranque o inicio de sesión del usuario).

Los [Timer]campos de sección utilizados aquí son los siguientes:

  • OnBootSec - inicie el servicio tantos segundos después de cada arranque.
  • OnUnitActiveSec- inicie el servicio varios segundos después de la última vez que se inició. Esto es lo que hace que el temporizador se repita y se comporte como un trabajo cron.
  • AccuracySec- Establece la precisión del temporizador. Los temporizadores son tan precisos como este campo establece, y el valor predeterminado es 1 minuto (emula cron). La razón principal para no exigir la mejor precisión es mejorar el consumo de energía: si SystemD puede programar la próxima ejecución para que coincida con otros eventos, debe reactivar la CPU con menos frecuencia. El 1msejemplo anterior no es ideal: generalmente establezco la precisión en 1(1 segundo) en mis trabajos programados de menos de un minuto, pero eso significaría que si mira el registro que muestra los mensajes de "Hola Mundo", verá que a menudo es tarde por 1 segundo. Si está de acuerdo con eso, sugiero configurar la precisión en 1 segundo o más.

Como habrás notado, este temporizador no imita a Cron tan bien, en el sentido de que el comando no comienza al comienzo de cada período de reloj de pared (es decir, no comienza en el décimo segundo del reloj, luego el 20 y así sucesivamente). En cambio, solo sucede cuando el temporizador transcurre. Si el sistema se inició a las 12:05:37, la próxima vez que se ejecute el comando será a las 12:05:47, luego a las 12:05:57, etc. Si está interesado en la precisión real del reloj de pared, puede desee reemplazar las OnBootSecy OnUnitActiveSeclos campos y en su lugar establecer una OnCalendarregla con el programa que desea (que por lo que yo entiendo, no puede ser más rápido que 1 segundo, usando el formato del calendario). El ejemplo anterior también se puede escribir como:

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

Última nota: como probablemente haya adivinado, la helloworld.timerunidad inicia la helloworld.serviceunidad porque tienen el mismo nombre (menos el sufijo del tipo de unidad). Este es el valor predeterminado, pero puede anularlo configurando el Unitcampo para la [Timer]sección.

Se pueden encontrar más detalles sangrientos en:

Guss
fuente
2
A menudo encuentro mejores respuestas como esta en la sección de comentarios. En mi humilde opinión, aunque el cron ha sido básico para los trabajos programados, esta respuesta debería ser la aceptada, ya que no es un "trabajo de
pirateo
2
excelente respuesta, debería ser la respuesta seleccionada
GuidedHacking
21

No necesita dos entradas cron, puede ponerlas en una con:

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

entonces en tu 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'\''"

Andrés
fuente
11
Nota: esto solo se ejecuta correctamente si el script tarda menos de un segundo en ejecutarse
rubo77
2
Rubo: si el trabajo tarda unos segundos (en lugar de mili o microsegundos) en completarse, entonces no lo ejecutaría cada treinta segundos para poder ejecutar dos veces por minuto. Entonces sí, comience con 30 y luego reste de eso el número aproximado de segundos por ejecución, si es mayor que 1 segundo.
Andrew
2
Esto tampoco ayuda si desea recibir informes de error para cada ejecución por separado.
joeln
joelin: el comando que doy no impide obtener datos de registro o salida, simplifiqué el comando para abordar la pregunta. Para capturar el registro, cada comando puede / debe tener la salida redirigida si necesita un registro, por ejemplo, script / rails runner -e production '\' 'Song.insert_latest' \ '' podría escribirse como script / rails runner -e production '\' 'Song.insert_latest' \ '' 2> & 1> / path_to_logfile y nuevamente se puede hacer para cada comando dentro de la única entrada cron.
Andrew
13

Puedes ver mi respuesta a esta pregunta similar

Básicamente, he incluido un script de bash llamado "runEvery.sh" que puede ejecutar con cron cada 1 minuto y pasar como argumentos el comando real que desea ejecutar y la frecuencia en segundos en que desea ejecutarlo.

algo como esto

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

shlomia
fuente
10

Usar reloj:

$ watch --interval .30 script_to_run_every_30_sec.sh
usuario7079817
fuente
¿Puedo usar algo como $ watch --interval .10 php some_file.php? o watchsolo funciona con archivos .sh?
Yevhenii Shashkov
Puedes correr cualquier cosa con reloj. Sin embargo, el intervalo es entre el final y el inicio del siguiente comando, por --interval .30lo que no se ejecutará dos veces por minuto. Es decir watch -n 2 "sleep 1 && date +%s", se incrementará cada 3s.
jmartori
tenga en cuenta que watchse diseñó para uso de terminal, por lo que, si bien puede funcionar sin un terminal (ejecutar con nohupluego cerrar sesión) o con un terminal falso (como screen), no tiene posibilidades para un comportamiento similar al cron, como recuperarse de una falla, reiniciar después del arranque, etc.
Guss
9

El trabajo Cron no se puede utilizar para programar un trabajo en intervalos de segundos. es decir, no puede programar un trabajo cron para que se ejecute cada 5 segundos. La alternativa es escribir un script de shell que use un sleep 5comando en él.

Cree un script de shell cada 5- segundos.sh usando bash while loop como se muestra a continuación.

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

Ahora, ejecute este script de shell en segundo plano usando nohupcomo se muestra a continuación. Esto seguirá ejecutando el script incluso después de cerrar sesión en su sesión. Esto ejecutará su script de shell backup.sh cada 5 segundos.

$ nohup ./every-5-seconds.sh &
Pankaj Shinde
fuente
44
El tiempo va a la deriva. Por ejemplo, si backup.shtarda 1,5 segundos en ejecutarse, se ejecutará cada 6,5 ​​segundos. Hay formas de evitar eso, por ejemplosleep $((5 - $(date +%s) % 5))
Keith Thompson
Soy nuevo en nohup, mientras ejecuto su ejemplo, nohup está devolviendo 'no existe tal archivo o directorio'. Después de algunas búsquedas, parece que te perdiste 'sh' después de nohup. De esta manera: $ nohup sh ./every-5-seconds.sh &
VHanded
6

Use fcron ( http://fcron.free.fr/ ): le brinda granularidad en segundos y mucho mejor y más funciones que cron (vixie-cron) y también estable. Solía ​​hacer cosas estúpidas, como tener alrededor de 60 scripts PHP ejecutados en una máquina en configuraciones muy estúpidas, ¡y todavía hizo su trabajo!

Adi Chiru
fuente
1
Confesiones de un desarrollador de PHP; )
Eric Kigathi
3
En realidad, confesiones de un ingeniero de sistemas que permite a los desarrolladores de PHP .... :)
Adi Chiru
6

en dir /etc/cron.d/

nuevo crea un archivo excute_per_30s

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

ejecutará cron cada 30 segundos

lingyfh
fuente
4

Actualmente estoy usando el siguiente método. Funciona sin problemas.

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

Si desea ejecutar cada N segundos y luego X habrá 60 / N y Y habrá N .

Gracias.

sujoshi
fuente
Probablemente desee cambiar YOUR_COMMANDSa YOUR_COMMANDS &, para que el comando se ejecute en segundo plano, de lo contrario, si el comando tarda más de una fracción de segundo, retrasará el próximo lanzamiento. Entonces, con X = 2 e Y = 30, si el comando tarda 10 segundos, se lanzará en el minuto y luego 40 segundos después, en lugar de 30. Kudus a @paxdiablo.
Guss
Por alguna razón, si omito la /bin/bash -cparte (incluidas las comillas de argumento), el script solo se ejecuta cada minuto, ignorando la iteración (en mi caso X=12y Y=5).
abiyi
3

El trabajo de Crontab se puede usar para programar un trabajo en minutos / horas / días, pero no en segundos. La alternativa :

Cree un script para ejecutar cada 30 segundos:

#!/bin/bash
# 30sec.sh

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

Use crontab -ey un crontab para ejecutar este script:

* * * * * /home/test/30sec.sh > /dev/null
szaier
fuente
2
Si entiendo esto correctamente, este script se ejecuta 30 veces y espera 30 segundos entre cada iteración. ¿Cómo tiene sentido ejecutarlo cada minuto en cron?
FuzzyAmi
2

Puede ejecutar ese script como servicio, reiniciar cada 30 segundos

Registrar un servicio

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

Pega el comando a continuación

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

Servicios de recarga

sudo systemctl daemon-reload

Habilitar el servicio

sudo systemctl enable YOUR_SERVICE_NAME

Inicia el servicio

sudo systemctl start YOUR_SERVICE_NAME

Verifique el estado de su servicio

systemctl status YOUR_SERVICE_NAME
Nguyen Nguyen
fuente
1

Gracias por todas las buenas respuestas. Para simplificar, me gustó la solución mixta, con el control en crontab y la división de tiempo en el script. Entonces, esto es lo que hice para ejecutar un script cada 20 segundos (tres veces por minuto). Línea Crontab:

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

Guión:

cd /home/a/b/checkAgenda

java -jar checkAgenda.jar
sleep 20
java -jar checkAgenda.jar 
sleep 20
java -jar checkAgenda.jar 
jfajunior
fuente
¿Qué representa 1-6?
Phantom007
@ Phantom007 1-6 representa de lunes a sábado, donde "-" es un rango y "0" es domingo. Aquí hay un buen enlace que explica muy bien todos los campos y dónde puede probarlo: " crontab.guru/# * _ * _ * _ * _ 1-6"
jfajunior
1

escribir un script de shell crear archivo .sh

nano cada 30 segundos.sh

y escribir guion

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

luego configure cron para este script crontab -e

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

este archivo cron call .sh cada 1 minuto y en el comando del archivo .sh se ejecuta 2 veces en 1 minuto

si desea ejecutar el script durante 5 segundos, reemplace 30 por 5 y cambie el bucle de esta manera: For (( i=1; i <= 12; i++ ))

cuando selecciona por cualquier segundo, calcule 60 / su segundo y escriba en el bucle For

Aditya Gosavi
fuente
0

Acabo de hacer una tarea similar y utilizo el siguiente enfoque:

nohup watch -n30 "kill -3 NODE_PID" &

Necesitaba tener un kill -3 periódico (para obtener el seguimiento de la pila de un programa) cada 30 segundos durante varias horas.

nohup ... & 

Esto está aquí para asegurarse de que no pierdo la ejecución de watch si pierdo el shell (problema de red, bloqueo de Windows, etc.)

Thomas
fuente
0

Eche un vistazo a frecuente-cron : es antiguo pero muy estable y puede reducirlo a microsegundos. En este momento, lo único que diría contra esto es que todavía estoy tratando de resolver cómo instalarlo fuera de init.d pero como un servicio systemd nativo, pero ciertamente hasta Ubuntu 18 se está ejecutando solo bien todavía usando init.d (la distancia puede variar en las últimas versiones). Tiene la ventaja adicional (?) De garantizar que no generará otra instancia del script PHP a menos que se haya completado una anterior, lo que reduce los posibles problemas de pérdida de memoria.

bnoeafk
fuente
-1

Ejecutar en un ciclo de shell, ejemplo:

#!/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
fuente
Incluso suponiendo que 60debería ser un 30, es posible que desee mover eso wget dentro de la ifdeclaración, de lo contrario, se ejecuta cada segundo. En cualquier caso, no estoy seguro de cómo esto es mejor que un simple sleep 30. Si estuviera monitoreando el tiempo real de UNIX en lugar de su contador, marcaría la diferencia.
paxdiablo
si se imprime el contador, puede averiguar el tiempo retrasado por EXCUTE COMMAND si NO ejecuta wget en segundo plano.
Lo Vega