Esegui letteralmente lo script bash ogni 3 giorni


8

Voglio eseguire uno script di shell letteralmente ogni 3 giorni. L'utilizzo di crontab con 01 00 */3 * *non soddisfa effettivamente la condizione, poiché verrebbe eseguito il 31 e quindi il 1 ° giorno del mese. La */3sintassi è la stessa di dire 1,4,7 ... 25,28,31.

Dovrebbero esserci modi per far sì che lo script stesso controlli le condizioni ed esca se non sono trascorsi 3 giorni. Quindi crontab esegue lo script ogni giorno, ma lo stesso script controllerebbe se sono trascorsi 3 giorni.

Ho anche trovato un po 'di codice, ma mi ha dato un errore di sintassi, qualsiasi aiuto sarebbe apprezzato.

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

main.sh: line 1: syntax error near unexpected token `"z"'
main.sh: line 1: `if (! (date("z") % 3)) {'

4
Cosa intendi esattamente? Come */3non funziona? "se non sono trascorsi 3 giorni": tre giorni da cosa? Si prega di modificare la tua domanda e chiarire.
terdon,

4
letteralmente? Questa sembra una domanda x / y e potresti voler davvero parlare di ciò che stai cercando di fare. Stai cercando di impedire a crontab di eseguire lo script?
Journeyman Geek,

1
Modificata la domanda per spiegare perché la soluzione crontab non funzionerà.
Taavi,

Qualcosa del genere date("z") % 3 == 0soffrirebbe di un problema simile: la condizione sarà falsa per i quattro giorni tra il 29 dicembre e il 3 gennaio, a meno che quel dicembre non facesse parte di un anno bisestile.
Rhymoid,

Risposte:


12

Per interrompere immediatamente ed uscire da uno script se l'ultima esecuzione non era ancora almeno un tempo specifico, è possibile utilizzare questo metodo che richiede un file esterno che memorizza la data e l'ora dell'ultima esecuzione.

Aggiungi queste righe all'inizio dello 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:

Non dimenticare di impostare un valore significativo come datefile=e di adattare il valore seconds=alle tue esigenze ( $((60*60*24*3))valuta 3 giorni).


Se non si desidera un file separato, è anche possibile memorizzare l'ultima ora di esecuzione nel timestamp di modifica dello script. Ciò significa tuttavia che apportare modifiche al file di script reimposterà il contatore 3 e verrà trattato come se lo script fosse stato eseguito correttamente.

Per implementarlo, aggiungi lo snippet di seguito nella parte superiore del file di 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:

Ancora una volta, non dimenticare di adattare il valore seconds=alle tue esigenze ( $((60*60*24*3))valuta 3 giorni).


Sì, la registrazione dell'ultima chiamata riuscita di un determinato programma richiede una sorta di archivio di dati esterno e il file system è una scelta ovvia per questo.
Kilian Foth,

Non è nemmeno necessario memorizzare la data: basta toccare il file ogni volta e controllare che sia il timestamp.
djsmiley2kStaysInside,

@ djsmiley2k Sì, hai ragione. Se il rischio che la modifica manuale del file di script causi il ripristino del ritardo è accettabile, è possibile utilizzare anche il timestamp di modifica. L'ho aggiunto alla mia risposta.
Byte Commander

Questo può essere modificato leggermente salvando il timestamp corrente nel file (anziché la data leggibile dall'uomo), in modo da poter ottenere il tempo trascorso con $[ $(date +%s) - $(< lastrun) ]. Se lo script viene eseguito da cron una volta al giorno, potrei aggiungere un po 'di gioco all'intervallo di tempo richiesto, in modo che se l'esecuzione dello script viene ritardata di un paio di secondi, la volta successiva non salterà un giorno intero. Cioè controlla ogni giorno se sono trascorse 71 ore da oslt.
ilkkachu,

Questa magia con datafile ha funzionato davvero, grazie mille!
Taavi,

12

Cron è davvero lo strumento sbagliato per questo. In realtà esiste uno strumento comunemente usato e poco apprezzato chiamato al quale potrebbe funzionare. at è progettato principalmente per l'uso interattivo e sono sicuro che qualcuno troverà un modo migliore per farlo.

Nel mio caso avrei lo script che sto eseguendo elencato in testjobs.txt e includendo una riga che legge.

Ad esempio, vorrei avere questo come testjobs.txt

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

Ho due comandi innocenti, che potrebbero essere i tuoi script di shell. Eseguo echo per assicurarmi di avere un output deterministico e data per confermare che il comando viene eseguito secondo necessità. Quando at esegue questo comando, finirà aggiungendo un nuovo lavoro a per per 3 giorni. (Ho provato con un minuto - che funziona)

Sono abbastanza sicuro che sarò chiamato per il modo in cui ho abusato, ma è uno strumento utile per programmare un comando da eseguire alla volta o x giorni dopo un comando precedente.


3
+1 come atsembra l'approccio migliore qui. Il problema con questo approccio è che ogni volta che esegui manualmente lo script, aggiungi un altro set di attività ogni 3 giorni at.
Dewi Morgan,

1
+1, ma un altro problema (oltre a quello indicato da @DewiMorgan) è che se uno script fallisce, tutti gli script successivi non verranno lanciati (se l'at è dopo il punto di errore), fino a quando non ci si rende conto che lo script ha fallito e rilanciarlo. Questo può essere cattivo o talvolta buono (es: fallisce perché una condizione non esiste più: è positivo che non riprovi dopo 3 giorni?). E ogni volta c'è una leggera deriva (qualche millisecondo se atè in cima, o potenzialmente molto di più se si attrova in fondo a una sceneggiatura di lunga esecuzione)
Olivier Dulac,

A può inviare e-mail .... Che potrebbe essere una soluzione per il fallimento. atq e atrm consentirebbero l'abbattimento di lavori errati forse? In teoria potresti scrivere una data specifica per a ma sembra inelegante e tenuta in mano.
Journeyman Geek,

Quindi avere un cronjob che controlla l'esistenza della prossima corsa in; se non ce n'è uno, aggiungine uno per 3 giorni e avvisa (o eseguilo immediatamente e ricontrolla).
djsmiley2kStaysInside,

3

Innanzitutto, il frammento di codice sopra riportato non è sintassi di Bash non valida, sembra Perl. In secondo luogo, il zparametro per datecausare l'output del fuso orario numerico. +%jè il numero del giorno. Hai bisogno:

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

Ma alla fine dell'anno vedrai ancora stranezze:

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

Potresti avere più fortuna nel tenere un conto in un file e testarlo / aggiornarlo.


1
Perl non ha una date()funzione integrata, ma assomiglia un po 'a date () in PHP (dove zè "Il giorno dell'anno (a partire da 0)")
ilkkachu,

2

Se puoi semplicemente lasciare lo script in esecuzione perennemente potresti fare:

while true; do

[inert code here]

sleep 259200
done

Questo ciclo è sempre vero, quindi eseguirà sempre il codice, quindi attenderà tre giorni prima di riavviare il ciclo.


Perché no while true?
Jonathan Leffler,

Hah! Buona pesca. Non ho bisogno di usarlo da molto tempo, ho dimenticato che esisteva lol
mkingsbu

2
Come atsoluzione, questo andrà alla deriva dal tempo di esecuzione dello script ad ogni esecuzione. Naturalmente questo può essere risolto risparmiando il tempo in cui lo script inizia e dormendo fino a 3 giorni da quello.
ilkkachu,

2

Puoi usare anacron invece di cron, è progettato esattamente per fare ciò di cui hai bisogno. Dalla manpage:

Anacron può essere utilizzato per eseguire comandi periodicamente, con una frequenza specificata in giorni. A differenza di cron (8), non presuppone che la macchina funzioni continuamente. Quindi, può essere utilizzato su macchine che non sono in esecuzione 24 ore al giorno, per controllare i lavori giornalieri, settimanali e mensili che sono generalmente controllati da cron.

Quando eseguito, Anacron legge un elenco di lavori da un file di configurazione, normalmente / etc / anacrontab (vedi anacrontab (5)). Questo file contiene l'elenco dei lavori controllati da Anacron. Ogni voce di lavoro specifica un periodo in giorni, un ritardo in minuti, un identificatore di lavoro univoco e un comando shell.

Per ogni lavoro, Anacron controlla se questo lavoro è stato eseguito negli ultimi n giorni, dove n è il periodo specificato per quel lavoro. In caso contrario, Anacron esegue il comando shell del lavoro, dopo aver atteso il numero di minuti specificato come parametro di ritardo.

Dopo la chiusura del comando, Anacron registra la data in un file di data e ora speciale per quel lavoro, in modo da sapere quando eseguirlo di nuovo. Per i calcoli dell'ora viene utilizzata solo la data. L'ora non è utilizzata.


Bene, testerò sicuramente Anacron. Mi chiedo perché sia ​​così sottovalutato se può fare una tale magia
Taavi,

La vera domanda: perché ormai cron non è stato sostituito con qualcosa di meglio? fcron esiste e penso che systemd stia lavorando alla propria soluzione, ma non sono aggiornato con lo stato attuale delle cose.
Twinkles,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.