¿Cómo puedo comenzar un cronjob 1 hora más tarde cada día?

16

Necesito comenzar un cronjob todos los días, pero una hora más tarde cada día. Lo que tengo hasta ahora funciona en su mayor parte, excepto por 1 día del año:

0 0 * * * sleep $((3600 * (10#$(date +\%j) \% 24))) && /usr/local/bin/myprog

Cuando el día del año es 365, el trabajo comenzará a las 5:00, pero el día siguiente (sin contar un año bisiesto) tendrá un día del año como 1, por lo que el trabajo comenzará a la 1:00. ¿Cómo puedo deshacerme de este caso de esquina?

abedul
fuente
1
¿Alguna razón para no comenzarlo cada 25 horas?
HalosGhost
77
¿Y cómo harías eso exactamente? * / 25 en la posición de la hora no lo resolverá.
abedul
@HalosGhost ¡Gracias por tu sugerencia! Escribí una implementación simple basada en at.
Giulio Muscarello

Respuestas:

23

Mi solución preferida sería comenzar el trabajo cada hora, pero hacer que el script compruebe si es hora de ejecutarlo o no y salir sin hacer nada 24 de 25 veces.

crontab:

0 * * * *    /usr/local/bin/myprog

en la parte superior de myprog:

[ 0 -eq $(( $(date +%s) / 3600 % 25 )) ] || exit 0

Si no desea realizar ningún cambio en el script en sí, también puede poner la comprobación de "tiempo de ejecución" en la entrada de crontab, pero crea una línea larga y desagradable:

0 * * * *    [ 0 -eq $(( $(date +\%s) / 3600 \% 25 )) ] && /usr/local/bin/myprog
Celada
fuente
1
'%' s debe escaparse con una barra diagonal inversa en el crontab.
abedul
@ abedul ¡Nunca lo supe! Supongo que nunca antes intenté incluir un% en un crontab. Gracias por la corrección, he editado la respuesta.
Celada
1
Esto se ve bien para mí. No tengo idea de lo que el OP quiere hacer con respecto al horario de verano (primavera, retraso), pero el OP debe hacer los ajustes correspondientes.
emory
Estaría un poco preocupado por redondear en la división. Si por alguna razón el tiempo que dateregresó del kernel fue 1msanterior al tiempo que esperaba que se ejecutara el script, la verificación daría un resultado incorrecto.
Kasperd
1
@kasperd No lo sé, supongo que podría tener razón sobre las diferentes CPU que informan diferentes tiempos. En cuanto a ntpd, se esfuerza por solo girar el reloj, no saltarlo, específicamente para evitar este tipo de problema, pero tienes razón, (o ntpdate) a veces puede saltar el tiempo hacia atrás. En cuanto a croncalcular mal su retraso de sueño, ¡estoy bastante seguro de que se consideraría un error! Aún así, punto tomado, y una solución alternativa sería programar el trabajo 30 minutos después de la hora, lo que es menos probable que cause el problema ... O agregue ± 1800 en la expresión aritmética antes de tomar el mod mod 3600.
Celada
7

Si su sistema tiene systemd, puede usar temporizadores para esto. Simplemente defina un nuevo servicio , que debe contener el comando / tarea que desea ejecutar, y luego cree un evento de temporizador con la OnUnitActiveSecopción:

[Unit]
Description=daily + 1 hour task

[Timer]
OnUnitActiveSec=25h # run 25 hours after service was last started
AccuracySec=10min

[Install]
WantedBy=timers.target

Use el mismo nombre para los archivos, excepto que en lugar de .serviceusarlos .timer.

Sintetizando:

  1. Cree un archivo llamado job.serviceen el /etc/systemd/system/directorio.
  2. Llénalo con la información necesaria. Puede verificar la configuración, utilizando systemctl status job.service.
  3. Crear un archivo llamado job.timeren /etc/systemd/system/.
  4. Rellene con la información requerida:

    [Unit]
    Description=daily + 1 hour task
    
    [Timer]
    OnUnitActiveSec=25h # run 25 hours after service was last started
    AccuracySec=10min
    
    [Install]
    WantedBy=timers.target
    
  5. Verifique el temporizador usando systemctl list-timers
  6. Hecho.
Braiam
fuente
Si me fuera a desviar de la simplicidad de cron, usaría launchd, que requeriría menos trabajo que su ejemplo para systemd.
abedul
6

Si no le importa usar algo que no sea cronjobs, sugeriría la utilidad menos conocida at. Simplemente escriba un script de envoltura que se programe para ejecutarse en 25 horas, y luego llame a su programa. Esta parece ser la solución más limpia.
Por ejemplo, podría escribir esto en ~ / script.sh:

echo "bash ~/script.sh" | at now + 25 hours
/usr/bin/yourprogram

Y luego simplemente corre bash ~/script.shuna vez.

Gracias a @HalosGhost por la idea de programar el trabajo una vez cada 25 horas.

Giulio Muscarello
fuente
2
Hay 2 problemas con el uso atpara este propósito: (1) si el trabajo no se ejecuta correctamente incluso una vez, probablemente también no se reprograme y luego no solo la próxima ejecución sino que todas las ejecuciones futuras se cancelen efectivamente hasta que un humano se dé cuenta, y (2) dado que el trabajo tarda un tiempo distinto de cero en ejecutarse, el uso ingenuo now + 25 hourssignifica que se ejecutará unos segundos (o más) más tarde cada vez, y si este retraso se acumula con el tiempo, eventualmente se ejecutará completamente en el mal hora.
Celada
2
Estás en lo correcto acerca del # 1; No estoy tan seguro sobre el # 2. Aunque no tengo ningún dato, no creo que el retraso entre el cambio del reloj y la activación del trabajo y posteriormente reprogramado sea lo suficientemente grande como para ser notable, especialmente teniendo en cuenta que la resolución atse limita a minutos y comienza todos los trabajos a las [hora]: 00.
Giulio Muscarello
1
@Celada: programar el siguiente attrabajo a primera hora en el script at evita esos problemas. No obstante, si se produce un error, se rompe toda la cadena, lo que puede o no ser deseado: si el caso de uso es "solo ejecutar esto cuando funciona", no reiniciar es una gran característica. Pero si el caso de uso "siempre se ejecuta, incluso si falla el último", entonces atno es la herramienta correcta.
obispo