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-5no campo dia da semana = segunda a sexta)./(Barra): Intervalos ou passos (ex:*/15no 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/