Executar script bash literalmente após cada 3 dias

8

Eu quero executar um script de shell literalmente a cada 3 dias. O uso do crontab with 01 00 */3 * *na verdade não atende à condição, porque seria executado no dia 31 e, em seguida, novamente no 1º dia de um mês. A */3sintaxe é o mesmo que dizer 1,4,7 ... 25,28,31.

Deve haver maneiras de fazer o próprio script verificar as condições e sair se 3 dias não se passaram. Portanto, o crontab executa o script todos os dias, mas o próprio script verifica se 3 dias se passaram.

Eu até encontrei algum código, mas ele me deu erro de sintaxe, qualquer ajuda seria 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
fonte
4
O que você quer dizer exatamente? Como */3não funciona? "se 3 dias não se passaram": três dias desde o que? Por favor edite sua pergunta e esclarecer.
terdon 25/09/16
4
literalmente literalmente? Parece uma pergunta x / y e você pode querer realmente falar sobre o que está tentando fazer. Você está tentando impedir que o crontab execute o script?
Journeyman Geek
1
Editou a pergunta para explicar por que a solução crontab não funciona.
Taavi
Algo assim date("z") % 3 == 0poderia sofrer de um problema semelhante: a condição será falsa pelos quatro dias entre 29 de dezembro e 3 de janeiro, a menos que esse mês de dezembro faça parte de um ano bissexto.
Rhymoid

Respostas:

12

Para abortar e sair imediatamente de um script, se a última execução ainda não foi há pelo menos um tempo específico, você pode usar este método que requer um arquivo externo que armazene a data e hora da última execução.

Adicione estas linhas na parte superior do seu 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:

Não se esqueça de definir um valor significativo datefile=e adaptar o valor seconds=às suas necessidades ( $((60*60*24*3))avalia em 3 dias).


Se você não quiser um arquivo separado, também poderá armazenar o último tempo de execução no carimbo de data / hora de modificação do seu script. Isso significa, no entanto, que fazer alterações no arquivo de script redefinirá o contador 3 e será tratado como se o script estivesse sendo executado com êxito.

Para implementar isso, adicione o snippet abaixo na parte superior do seu arquivo 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:

Mais uma vez, não se esqueça de adaptar o valor seconds=às suas necessidades ( $((60*60*24*3))avalia em 3 dias).

Byte Commander
fonte
Sim, gravar a última chamada bem-sucedida de um programa específico requer algum tipo de armazenamento de dados externo, e o sistema de arquivos é uma escolha óbvia para isso.
22716 Kilian Foth
Nem precisa armazenar a data - basta tocar no arquivo todas as vezes e verificar a hora.
precisa saber é o seguinte
@ djsmiley2k Sim, você está certo. Se o risco de modificar o arquivo de script manualmente causar o atraso da redefinição for aceitável, também é possível usar o carimbo de data / hora da modificação. Eu adicionei isso à minha resposta.
Byte Commander
Isso pode ser jogado um pouco, salvando o carimbo de data / hora atual no arquivo (em vez da data legível por humanos), para que você possa obter o tempo decorrido $[ $(date +%s) - $(< lastrun) ]. Se o script for executado a partir do cron uma vez por dia, posso adicionar uma folga ao intervalo de tempo necessário, para que, se a execução do script for atrasada em alguns segundos, a próxima vez não pulará um dia inteiro. Ou seja, verifique todos os dias se 71 horas passaram oslt.
ilkkachu
Essa magia com o datefile realmente funcionou, muito obrigado!
Taavi
12

Cron é realmente a ferramenta errada para isso. Na verdade, existe uma ferramenta comumente usada e pouco usada, na qual pode funcionar. at é projetado principalmente para uso interativo e tenho certeza de que alguém encontraria uma maneira melhor de fazer isso.

No meu caso, eu teria o script que estou executando listado em testjobs.txt e incluiria uma linha que lê.

Como exemplo, eu teria isso como testjobs.txt

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

Eu tenho dois comandos inocentes, que podem ser seus shellscripts. Eu corro echo para garantir uma saída determinística e data para confirmar se o comando é executado conforme necessário. Quando at executa esse comando, ele termina adicionando um novo trabalho a por 3 dias. (Eu testei com um minuto - o que funciona)

Eu tenho certeza que eu vou ser chamado pela maneira que eu abusado pelo, mas é uma ferramenta útil para agendar um comando a ser executado em um momento ou x dias após um comando anterior.

Journeyman Geek
fonte
3
+1 como atparece a melhor abordagem aqui. O problema dessa abordagem é que, sempre que você executa o script manualmente, adiciona outro conjunto de attarefas a cada três dias .
Dewi Morgan
1
+1, mas outro problema (além do apontado por @DewiMorgan) é que, se um script falhar, todo o script subsequente não será iniciado (se at for após o ponto de falha), até que se perceba que o script foi falhou e reinicie-o. Isso pode ser ruim ou às vezes bom (ex: falha porque uma condição não está mais lá: é bom que ela não tente novamente em três dias?). E há um ligeiro desvio de cada vez (alguns milissegundos se atestá no topo, ou potencialmente muito mais se atestá perto do fundo de um roteiro de longa execução)
Olivier Dulac
No pode enviar e-mails .... O que pode ser uma solução para falhas. atq e atrm permitiriam abater trabalhos errantes, talvez? Em teoria, você pode escrever uma data específica para at, mas isso parece deselegante e de mão.
Journeyman Geek
Portanto, tenha um cronjob que verifique a existência da próxima execução em at; se não houver, adicione um por 3 dias e avise (ou execute-o imediatamente e verifique novamente).
precisa saber é o seguinte
3

Primeiro, o fragmento de código acima é uma sintaxe inválida do Bash, se parece com Perl. Segundo, o zparâmetro para dategerar o fuso horário numérico. +%jé o número do dia. Você precisa:

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

Mas você ainda verá estranheza no final do ano:

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

Você pode ter mais sorte mantendo uma contagem em um arquivo e testando / atualizando isso.

waltinator
fonte
1
Perl não tem uma date()função interna, mas que parece um pouco com date () em PHP (onde zé "o dia do ano (a partir de 0)")
ilkkachu
2

Se você puder simplesmente deixar o script em execução perpetuamente, poderá fazer:

while true; do

[inert code here]

sleep 259200
done

Esse loop é sempre verdadeiro, portanto, ele sempre executa o código e aguarda três dias antes de iniciar o loop novamente.

mkingsbu
fonte
Por que não while true?
Jonathan Leffler
Hah! Boa pegada. Eu não precisava usar isso em um longo tempo, eu esqueci que existia lol
mkingsbu
2
Como atsolução, isso varia conforme o tempo de execução do script a cada execução. É claro que isso pode ser contornado, economizando o tempo em que o script é iniciado e adormecendo até três dias depois.
ilkkachu
2

Você pode usar o anacron em vez do cron, ele foi projetado exatamente para fazer o que você precisa. Na página de manual:

O Anacron pode ser usado para executar comandos periodicamente, com uma frequência especificada em dias. Diferentemente do cron (8), ele não assume que a máquina esteja funcionando continuamente. Portanto, ele pode ser usado em máquinas que não estão funcionando 24 horas por dia, para controlar tarefas diárias, semanais e mensais que geralmente são controladas pelo cron.

Quando executado, o Anacron lê uma lista de trabalhos de um arquivo de configuração, normalmente / etc / anacrontab (consulte anacrontab (5)). Este arquivo contém a lista de trabalhos que a Anacron controla. Cada entrada de trabalho especifica um período em dias, um atraso em minutos, um identificador de trabalho exclusivo e um comando shell.

Para cada trabalho, a Anacron verifica se esse trabalho foi executado nos últimos n dias, em que n é o período especificado para esse trabalho. Caso contrário, o Anacron executa o comando shell do trabalho, depois de aguardar o número de minutos especificado como o parâmetro delay.

Após a saída do comando, o Anacron registra a data em um arquivo de carimbo de data / hora especial para esse trabalho, para que ele saiba quando executá-lo novamente. Somente a data é usada para os cálculos de tempo. A hora não é usada.

Twinkles
fonte
Bom, certamente testarei o Anacron. Admira por isso que é tão subestimado se ele pode fazer tal mágica
Taavi
A verdadeira questão: por que o cron não foi substituído por algo melhor agora? O fcron existe e acho que o systemd está trabalhando em sua própria solução, mas não estou atualizado com o estado atual das coisas.
Twinkles