2

Decifrando a sintaxe do CRON: A lógica dos 5 campos e as 3 maiores armadilhas no Linux

O cron é um dos utilitários mais antigos do Unix (criado na década de 1970) e ainda hoje gerencia tarefas essenciais em praticamente todo servidor Linux em produção.

Apesar de parecer simples à primeira vista (* * * * * comando), ele esconde comportamentos lógicos peculiares que pegam até desenvolvedores seniores desprevenidos.

Abaixo está um raio-X completo de como a sintaxe funciona por baixo dos panos, a pegadinha clássica do operador "OU" e os 3 erros mais comuns em ambientes de produção.


1. A Anatomia dos 5 Campos

Uma entrada tradicional do crontab possui exatamente 5 campos de tempo seguidos pelo comando:

┌───────────── minuto (0 - 59)
│ ┌────────────── hora (0 - 23)
│ │ ┌─────────────── dia do mês (1 - 31)
│ │ │ ┌──────────────── mês (1 - 12)
│ │ │ │ ┌───────────────── dia da semana (0 - 6, onde 0 = Domingo)
│ │ │ │ │
* * * * * comando_a_executar

Operadores Especiais:

  • * (Asterisco): Todo valor possível naquele campo.
  • , (Vírgula): Lista de valores discretos (ex: 1,15,30).
  • - (Hífen): Faixa contínua (ex: 1-5 no campo dia da semana = segunda a sexta).
  • / (Barra): Intervalos ou passos (ex: */15 no campo minuto = a cada 15 minutos; 0 9-18/2 * * * = a cada 2 horas no horário comercial).

2. A Maior Pegadinha do Cron: "Dia do Mês" vs "Dia da Semana"

Na maioria dos campos do cron, a relação entre eles é um E lógico (AND):

  • 30 2 * * * = minuto 30 E hora 2 (todo dia às 02:30).

Porém, existe uma exceção histórica documentada no POSIX: se você definir tanto o Dia do Mês quanto o Dia da Semana, o cron não faz um AND, ele faz um OU lógico (OR)!

O Exemplo Prático:

Se você configurar:

0 2 15 * 5 /usr/local/bin/backup.sh

Você provavelmente esperava que o script rodasse "no dia 15 do mês, apenas se for uma sexta-feira".

O que o Linux realmente faz:
Ele executa no dia 15 de qualquer mês E TAMBÉM em todas as sextas-feiras do mês às 02:00!

Como resolver se você realmente precisa de um "AND"?

Deixe o dia da semana como * e faça o teste da data dentro do próprio comando via shell:

# Executa apenas no dia 15 SE for sexta-feira (date +%u retorna 5 para sexta)
0 2 15 * * [ "$(date +\%u)" -eq 5 ] && /usr/local/bin/backup.sh

3. As 3 Maiores Armadilhas em Produção

⚠️ Armadilha 1: O Ambiente Mínimo (Environment Stripping)

Quando o cron executa seu script, ele NÃO carrega o seu .bashrc nem o seu .profile. Ele roda com um PATH mínimo (geralmente apenas /usr/bin:/bin).

Se o seu script depende de binários em /usr/local/bin, NVM, pyenv ou Docker, ele falha com command not found.

Como evitar:
Sempre declare os caminhos absolutos dos binários ou force o PATH no topo do próprio crontab:

SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

0 3 * * * /usr/bin/python3 /opt/app/job.py

⚠️ Armadilha 2: O Caractere % no Crontab

No formato do crontab, o caractere % é tratado como quebra de linha (\n) a menos que você o escape com barra invertida (\%).

Se você tentar rodar:

0 0 * * * backup.sh > /logs/backup_$(date +%Y%m%d).log  # ❌ QUEBRA!

O cron vai cortar o comando antes do % e enviar o restante como entrada padrão (stdin).

O correto:

0 0 * * * backup.sh > /logs/backup_$(date +\%Y\%m\%d).log  # ✅ FUNCIONA

⚠️ Armadilha 3: Falta de Redirecionamento de Logs

Se um cron job falhar e não tiver redirecionamento de saída, o Linux tentará enviar um e-mail local via sendmail/postfix para a caixa do usuário (/var/mail/root). Se o servidor não tiver MTA configurado, o erro simplesmente evapora no vácuo.

Sempre redirecione:

0 4 * * * /opt/rotina.sh >> /var/log/rotina.log 2>&1

4. Como Avaliar Próximas Execuções via Código (Python)

Se você precisa calcular em código quando uma tarefa vai rodar sem instalar bibliotecas pesadas:

from datetime import datetime, timedelta

def proximo_minuto_valido(cron_expr):
    # Exemplo simplificado para minuto e hora
    partes = cron_expr.split()
    min_expr, hora_expr = partes[0], partes[1]
    
    agora = datetime.now().replace(second=0, microsecond=0)
    candidato = agora + timedelta(minutes=1)
    
    # Validador de passos (ex: */15)
    while True:
        min_ok = (min_expr == "*") or (min_expr.startswith("*/") and candidato.minute % int(min_expr[2:]) == 0) or (str(candidato.minute) == min_expr)
        hora_ok = (hora_expr == "*") or (str(candidato.hour) == hora_expr)
        
        if min_ok and hora_ok:
            return candidato
        candidato += timedelta(minutes=1)

print("Próxima execução:", proximo_minuto_valido("*/15 * * * *"))

Qual foi a pegadinha mais bizarra que você já enfrentou configurando rotinas no cron?


Tradutor visual de expressões CRON com simulação das próximas 5 execuções: tecmestre.com.br/gerador-de-cron/

Carregando publicação patrocinada...