Programe o último dia de cada mês

10

Li uma instrução para agendar um script no último dia do mês:

Nota:
O leitor astuto pode estar se perguntando como seria possível definir um comando para executar no último dia de cada mês, porque não é possível definir o valor de um dia para cobrir todos os meses. Esse problema afetou os programadores do Linux e Unix e gerou várias soluções diferentes. Um método comum é adicionar uma instrução if-then que usa o comando date para verificar se a data de amanhã é 01:

00 12 * * * if [`date +%d -d tomorrow` = 01 ] ; then ; command1

Isso verifica todos os dias às 12 horas para ver se é o último dia do mês e, nesse caso, o cron executa o comando.

insira a descrição da imagem aqui

Como [`date +%d -d tomorrow` = 01 ]funciona?
É correto afirmar then; command1?

Cálculo
fonte
Tem certeza de que é literalmente o que diz? Como está escrito aqui, de fato não funciona.
Michael Homer
Eu publiquei o instantâneo. @ MichaelHomer
Cálculo
Obrigado! Eu brinquei com a formatação para fazer a correspondência - ainda não está bem, mas é o que a imagem diz.
Michael Homer
1
Desaparecido ; endif?
Danblack 29/10
Isso não funciona. Ele contém erros de sintaxe: sem espaço após [e não fino final. Além disso, %é especial em crontabs.
Kusalananda

Respostas:

17

Resumo

O código correto deve ser:

#!/bin/sh
[ "$#" -eq 0 ] && echo "Usage: $0 command [args]" && exit 1
[ "$(date -d tomorrow +'%d')" = 01 ] || exit 0
exec "$@"

Chame esse script end_of_month.she a chamada no cron é simplesmente:

00 12 28-31 * * /path/to/script/end_of_month.sh command

Isso executaria o script end_of_month(que internamente verificará se o dia é o último dia do mês) apenas nos dias 28, 29, 30 e 31. Não há necessidade de verificar o final do mês em nenhum outro dia.

Post antigo.

Essa é uma citação do livro "Linux Command Line and Shell Scripting Bible" de Richard Blum, Christine Bresnahan, pp. 442, terceira edição, John Wiley & Sons © 2015.

Sim, é o que diz, mas está errado / incompleto:

  • Faltando um fechamento fi.
  • Precisa de espaço entre [e o seguinte `.
  • É altamente recomendável usar $ (…) em vez de `…`.
  • É importante que você use aspas em expansões como"$(…)"
  • Há um adicional ;apósthen

Como eu sei? (bem, por experiência), mas você pode tentar o Shellcheck . Cole o código do livro (após os asteriscos) e ele mostrará os erros listados acima, além de um "erro de digitação". Um script sem erros no Shellcheck é o seguinte:

#!/bin/sh if [ "$(date +%d -d tomorrow)" = 01 ] ; then script.sh; fi

Esse site funciona porque o que foi escrito é "código shell". Essa é uma sintaxe que funciona em muitos shells.

Alguns problemas que o shellcheck não menciona são:

  • Supõe-se que o comando date seja a versão da data GNU. Aquele com uma -dopção que aceita tomorrowcomo valor (busybox tem uma opção -d, mas não entende o amanhã e o BSD tem uma -dopção, mas não está relacionado à "exibição" de tempo).

  • É melhor definir o formato depois de todas as opções date -d tomorrow +'%d'.

  • O horário de início do cron é sempre no horário local, o que pode fazer com que um trabalho seja iniciado 1 hora antes ou depois da contagem exata do dia, se o horário de verão estiver definido ou não.

O que fizemos foi um script de shell que poderia ser chamado com cron. Podemos modificar ainda mais o script para aceitar argumentos do programa ou comando para executar, assim (finalmente, o código correto):

#!/bin/sh
[ "$#" -eq 0 ] && echo "Usage: $0 command [args]" && exit 1
[ "$(date -d tomorrow +'%d')" = 01 ] || exit 0
exec "$@"

Chame esse script end_of_month.she a chamada no cron é simplesmente:

00 12 28-31 * * /path/to/script/end_of_month.sh command

Isso executaria o script end_of_month(que internamente verificará se o dia é o último dia do mês) apenas nos dias 28, 29, 30 e 31. Não há necessidade de verificar o final do mês em nenhum outro dia.

Verifique se o caminho correto está incluído. O PATH dentro do cron não será (provavelmente) o mesmo que o PATH do usuário.

Observe que há um script de final de mês testado (como indicado abaixo) que poderia chamar muitos outros utilitários ou scripts.

Isso também evitará o problema adicional que o cron gera com a linha de comando completa:

  • Cron divide a linha de comando em qualquer um, %mesmo que citado por 'ou "(apenas um \funciona aqui). Essa é uma maneira comum pela qual os trabalhos cron falham.

Você pode testar se o end_of_month.shscript funciona corretamente em alguma data (sem esperar até o final do mês para descobrir que não funciona) testando-o com tempo de folga:

$ faketime 2018/10/31 ./end_of_month echo "Command will be executed...."
Command will be executed....
Isaac
fonte
o ast-open date(ou o datebuilt-in do ksh93 se o ksh93 foi construído como parte do ast-open) suporta date -d tomorrow +%sou date +%s tomorrow).
Stéphane Chazelas
2
A experiência provou (muitas vezes) que é muito melhor testar o script executando corretamente com tempo de folga do que esperar até o final do mês para descobrir que o trabalho agendado não funcionou. Como descobrir que isso * * * * * echo "$(date -u +'date %c')" >>~/testfilenão funciona porque se esqueceu de citar o \%(que se torna difícil de depurar se apenas uma tentativa por mês for possível). @Kusalananda
Isaac
8

Supondo que os erros de sintaxe sejam corrigidos e o comando reformulado levemente para ser menos detalhado:

00 12 28-31 * * [ "$( date -d tomorrow +\%d )" != "01" ] || command1

Isso é executado date +%d -d tomorrow(supondo que dateseja usado o GNU ) para obter a data de amanhã como um número de dois dígitos. Se o número não for 01, hoje não é o último dia do mês. Nesse caso, os testes bem-sucedido e command1é não executado. O trabalho é executado ao meio-dia nos dias que podem ser o último dia do mês.

O comando original:

00 12 * * * if [`date +%d -d tomorrow` = 01 ] ; then ; command1

Isso tem alguns problemas:

  • Não há espaço depois [.
  • A ;diretamente depois then.
  • %é especial nas especificações de tarefas do cron e deve ser escapado como \%(consulte man 5 crontab).
  • Não há final fino final que combine com o if.
Kusalananda
fonte
2
O problema de usar em [ ... ] && command1vez de if...é que, nos dias que não são o último dia do mês, o trabalho cron termina com um status de saída diferente de zero e essa falha pode ter que ser relatada. Usar [ "$(...)" != 01 ] || command1é outra maneira de evitar o problema.
Stéphane Chazelas
1
Outro problema com o código original é ;entre thene command1.
Stéphane Chazelas
@ StéphaneChazelas Outra razão pela qual eu não gosto de one-liners, eles são difíceis de ler.
Kusalananda
A diferença de horário entre execuções consecutivas do comando pode não ser uma quantidade inteira de (24 horas) dias, pois uma alteração no horário de verão alterará o horário de início do cron.
Isaac
3
@cat Não importa como você citar, seja "ou '(exceto \), o sinal de porcentagem %vai fazer cron quebrar a linha em duas partes. Essa é uma maneira usual de fazer com que o cron falhe.
Isaac
-1

Para agendar o último dia de cada mês, você pode tentar: 0 0 15,L * *.

Kylin Sky
fonte