Ejecute el script bash literalmente cada 3 días.

8

Quiero ejecutar un script de shell literalmente cada 3 días. El uso de crontab con 01 00 */3 * *no cumplirá la condición, ya que se ejecutará el 31 y luego nuevamente el primer día de un mes. La */3sintaxis es la misma que decir 1,4,7 ... 25,28,31.

Debería haber formas de hacer que el script compruebe las condiciones y salga si no han pasado 3 días. Entonces crontab ejecuta el script todos los días, pero el script en sí mismo verificará si han pasado 3 días.

Incluso encontré algo de código, pero me dio un error de sintaxis, cualquier ayuda sería apreciada.

if (! (date("z") % 3)) {
     exit;
}

main.sh: line 1: syntax error near unexpected token `"z"'
main.sh: line 1: `if (! (date("z") % 3)) {'
Taavi
fuente
44
¿Qué quieres decir exactamente? ¿Cómo */3no funciona? "si no han pasado 3 días": ¿tres días desde qué? Por favor, editar su pregunta y aclarar.
terdon
44
literalmente literalmente? Parece una pregunta x / y es posible que desee hablar sobre lo que está tratando de hacer. ¿Estás tratando de evitar que crontab ejecute el script?
Journeyman Geek
1
Editó la pregunta para explicar por qué la solución crontab no funcionará.
Taavi
Algo así date("z") % 3 == 0sufriría un problema similar: la condición será falsa durante los cuatro días entre el 29 de diciembre y el 3 de enero, a menos que ese diciembre sea parte de un año bisiesto.
Rhymoid

Respuestas:

12

Para abortar inmediatamente y salir de un script si la última ejecución aún no fue al menos hace un tiempo específico, puede usar este método que requiere un archivo externo que almacene la última fecha y hora de ejecución.

Agregue estas líneas a la parte superior de su script Bash:

#!/bin/bash

# File that stores the last execution date in plain text:
datefile=/path/to/your/datefile

# Minimum delay between two script executions, in seconds. 
seconds=$((60*60*24*3))

# Test if datefile exists and compare the difference between the stored date 
# and now with the given minimum delay in seconds. 
# Exit with error code 1 if the minimum delay is not exceeded yet.
if test -f "$datefile" ; then
    if test "$(($(date "+%s")-$(date -f "$datefile" "+%s")))" -lt "$seconds" ; then
        echo "This script may not yet be started again."
        exit 1
    fi
fi

# Store the current date and time in datefile
date -R > "$datefile"

# Insert your normal script here:

No olvide establecer un valor significativo como datefile=y adaptar el valor seconds=a sus necesidades (se $((60*60*24*3))evalúa en 3 días).


Si no desea un archivo separado, también puede almacenar el último tiempo de ejecución en la marca de tiempo de modificación de su script. Sin embargo, eso significa que al hacer cualquier cambio en su archivo de script se restablecerá el contador 3 y se tratará como si el script se estuviera ejecutando correctamente.

Para implementar eso, agregue el fragmento a continuación en la parte superior de su archivo de script:

#!/bin/bash

# Minimum delay between two script executions, in seconds. 
seconds=$((60*60*24*3))

# Compare the difference between this script's modification time stamp 
# and the current date with the given minimum delay in seconds. 
# Exit with error code 1 if the minimum delay is not exceeded yet.
if test "$(($(date "+%s")-$(date -r "$0" "+%s")))" -lt "$seconds" ; then
    echo "This script may not yet be started again."
    exit 1
fi

# Store the current date as modification time stamp of this script file
touch -m -- "$0"

# Insert your normal script here:

Nuevamente, no olvide adaptar el valor de seconds=sus necesidades (se $((60*60*24*3))evalúa en 3 días).

Byte Commander
fuente
Sí, registrar la última invocación exitosa de un programa en particular requiere algún tipo de almacenamiento de datos externo, y el sistema de archivos es una opción obvia para eso.
Kilian Foth
Ni siquiera necesita almacenar la fecha: solo toque el archivo cada vez y verifique su marca de tiempo.
djsmiley2kStaysInside
@ djsmiley2k Sí, tienes razón. Si el riesgo de que la modificación manual del archivo de secuencia de comandos provoque un retraso en el restablecimiento es aceptable, también se puede usar la marca de tiempo de modificación. Agregué eso a mi respuesta.
Byte Commander
Esto se puede mejorar un poco guardando la marca de tiempo actual en el archivo (en lugar de la fecha legible por humanos), para que pueda obtener el tiempo transcurrido con $[ $(date +%s) - $(< lastrun) ]. Si el script se ejecuta desde cron una vez al día, podría agregar algo de holgura al intervalo de tiempo requerido, de modo que si la ejecución del script se retrasa un par de segundos, la próxima vez no se saltará un día completo. Eso es comprobar todos los días si han pasado 71 horas.
ilkkachu
Esta magia con datefile realmente funcionó, ¡muchas gracias!
Taavi
12

Cron realmente es la herramienta incorrecta para esto. En realidad, existe una herramienta comúnmente utilizada y poco querida llamada en la que podría funcionar. at está diseñado principalmente para uso interactivo y estoy seguro de que alguien encontrará una mejor manera de hacerlo.

En mi caso, tendría el script que estoy ejecutando en testjobs.txt, e incluiría una línea que lea.

Como ejemplo, tendría esto como testjobs.txt

echo "cat" >> foo.txt
date >> foo.txt
at now + 3 days < testjobs.txt

Tengo dos comandos inocentes, que podrían ser sus shellscripts. Ejecuto echo para asegurarme de que tengo una salida determinista y la fecha para confirmar que el comando se ejecuta según sea necesario. Cuando ejecuta este comando, terminará agregando un nuevo trabajo a durante 3 días. (Lo probé con un minuto, lo que funciona)

Estoy bastante seguro de que se me llamará por la forma en que he abusado, pero es una herramienta útil para programar un comando para que se ejecute en un momento o x días después de un comando anterior.

Journeyman Geek
fuente
3
+1 como atparece el mejor enfoque aquí. El problema con este enfoque es que cada vez que ejecutas manualmente el script, agregas otro conjunto de attareas cada 3 días .
Dewi Morgan
1
+1, pero otro problema (además del señalado por @DewiMorgan) es que si falla una secuencia de comandos, no se iniciará toda la secuencia de comandos posterior (si el at está después del punto de falla), hasta que uno se dé cuenta de que la secuencia de comandos tenía falló y lo relancé. Esto puede ser malo o, a veces, bueno (p. Ej .: falla porque una condición ya no existe: ¿es bueno que no vuelva a intentarlo en 3 días?). Y hay una ligera deriva cada vez (unos pocos milisegundos si atestá en la parte superior, o potencialmente mucho más si atestá cerca de la parte inferior de un script de ejecución larga)
Olivier Dulac
En puede enviar correos electrónicos ... lo que podría ser una solución para el fracaso. ¿atq y atrm permitirían eliminar trabajos errantes, tal vez? En teoría, podría escribir una fecha específica para at pero eso parece poco elegante y fácil de manejar.
Journeyman Geek
Así que tenga un cronjob que verifique la existencia de la próxima ejecución en; Si no hay uno, agregue uno por 3 días y advierta (o ejecútelo inmediatamente y verifique nuevamente).
djsmiley2kStaysInside
3

Primero, el fragmento de código anterior es una sintaxis Bash no válida, se parece a Perl. En segundo lugar, el zparámetro a datehace que genere la zona horaria numérica. +%jEs el número del día. Necesitas:

if [[ ! $(( $(date +%j) % 3 )) ]] ;then
     exit
fi

Pero todavía verás extrañeza al final del año:

$ for i in 364 365  1 ; do echo "Day $i, $(( $i % 3 ))"; done
Day 364, 1
Day 365, 2
Day 1, 1

Es posible que tenga más suerte manteniendo un conteo en un archivo y probando / actualizando eso.

Waltinator
fuente
1
Perl no tiene una date()función incorporada, pero se parece un poco a date () en PHP (donde zes "El día del año (a partir de 0)")
ilkkachu
2

Si puede dejar el script ejecutándose perpetuamente, puede hacer lo siguiente:

while true; do

[inert code here]

sleep 259200
done

Este bucle siempre es verdadero, por lo que siempre ejecutará el código, luego esperará tres días antes de comenzar el bucle nuevamente.

mkingsbu
fuente
¿Por qué no while true?
Jonathan Leffler
Ja! Buena atrapada. No he necesitado usar eso en mucho tiempo, olvidé que existía lol
mkingsbu
2
Como atsolución, esto dependerá del tiempo de ejecución del script en cada ejecución. Por supuesto, eso se puede solucionar ahorrando el tiempo cuando comienza el script y durmiendo hasta 3 días después de eso.
ilkkachu
2

Puede usar anacron en lugar de cron, está diseñado exactamente para hacer lo que necesita. Desde la página del manual:

Anacron se puede usar para ejecutar comandos periódicamente, con una frecuencia especificada en días. A diferencia de cron (8), no supone que la máquina esté funcionando continuamente. Por lo tanto, se puede usar en máquinas que no funcionan las 24 horas del día, para controlar trabajos diarios, semanales y mensuales que usualmente controlan cron.

Cuando se ejecuta, Anacron lee una lista de trabajos de un archivo de configuración, normalmente / etc / anacrontab (ver anacrontab (5)). Este archivo contiene la lista de trabajos que controla Anacron. Cada entrada de trabajo especifica un período en días, un retraso en minutos, un identificador de trabajo único y un comando de shell.

Para cada trabajo, Anacron verifica si este trabajo se ha ejecutado en los últimos n días, donde n es el período especificado para ese trabajo. Si no, Anacron ejecuta el comando de shell del trabajo, después de esperar la cantidad de minutos especificada como parámetro de retraso.

Después de que se cierra el comando, Anacron registra la fecha en un archivo de marca de tiempo especial para ese trabajo, para que pueda saber cuándo ejecutarlo nuevamente. Solo se usa la fecha para los cálculos de tiempo. La hora no se usa.

Centelleos
fuente
Bien, seguro probaré Anacron. Me pregunto por qué está tan subestimado si puede hacer tanta magia
Taavi
La verdadera pregunta: ¿por qué el cron no ha sido reemplazado por algo mejor ahora? fcron existe y creo que systemd está trabajando en su propia solución, pero no estoy al día con el estado actual de las cosas.
Twinkles