Shell Script para Produção: Boas práticas para blindar automações contra falhas silenciosas
Todo desenvolvedor ou SysAdmin já escreveu um script rápido em Bash para rodar no Cron da madrugada: backup de banco de dados, rotação de logs, deploy de containers ou sincronização de arquivos.
O problema é que o Bash, por padrão, é extremamente permissivo:
- Se um comando no meio do script falha, ele continua executando as linhas seguintes como se nada tivesse acontecido.
- Se uma variável estiver vazia ou com erro de digitação (
rm -rf "$DIRETORIO/*"quando$DIRETORIOestá vazia), ele pode apagar a raiz do sistema. - Se dois cron jobs demoram mais que o esperado, eles começam a rodar simultaneamente, gerando deadlocks ou corrupção de dados.
Abaixo está um resumo prático das técnicas essenciais para transformar scripts improvisados em ferramentas robustas e prontas para produção.
1. O Cabeçalho "Modo Estrito" (Strict Mode)
A primeira linha de qualquer script de produção deve ser o modo estrito:
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'
O que cada flag faz:
-e(errexit): Interrompe o script imediatamente se qualquer comando retornar um código de saída diferente de zero (exit status != 0).- Exceção controlada: Se você espera que um comando possa falhar legitimamente, use
comando || trueou envolva em umif.
- Exceção controlada: Se você espera que um comando possa falhar legitimamente, use
-u(nounset): Trata variáveis não inicializadas como erro fatal. Se você digitar$USER_NANEem vez de$USER_NAME, o Bash interrompe a execução antes de fazer besteira.-o pipefail: Em pipelines (cmd1 | cmd2 | cmd3), o Bash por padrão só avalia o status do último comando. Compipefail, se ocmd1falhar, o pipeline inteiro é considerado falho.IFS=$'\n\t': Altera o Separador Interno de Campos para que espaços comuns não dividam argumentos ao iterar listas ou caminhos de arquivos.
2. Limpeza Automática e Tratamento de Erros com trap
Em scripts que geram arquivos temporários, travam locks ou sobem processos em segundo plano, se o usuário der Ctrl+C ou o script quebrar no meio, os arquivos temporários ficam órfãos para sempre.
O comando trap intercepta sinais do sistema e garante a execução de funções de encerramento:
# Cria um arquivo temporário exclusivo
TMP_FILE=$(mktemp /tmp/relatorio_XXXXXX.tmp)
# Função executada SEMPRE que o script terminar (seja com sucesso ou com erro)
cleanup() {
echo "[INFO] Limpando recursos temporários..."
rm -f "$TMP_FILE"
}
trap cleanup EXIT
# Função executada especificamente quando ocorre um ERRO não tratado
error_handler() {
local linha="$1"
local comando="$2"
echo "[ERRO CRÍTICO] Falha no comando '$comando' na linha $linha" >&2
}
trap 'error_handler $LINENO "$BASH_COMMAND"' ERR
3. Prevenindo Execução Simultânea com flock
Se o seu script faz backup de banco e demora 15 minutos, mas o cron roda a cada 10 minutos, você terá dois backups pesados rodando ao mesmo tempo.
A forma mais limpa de impedir isso no Linux sem criar arquivos .pid manuais (que ficam presos se o servidor reiniciar) é usar o flock:
LOCK_FILE="/var/lock/meu_script_backup.lock"
# Abre o descritor de arquivo 200 no lockfile
exec 200>"$LOCK_FILE"
# Tenta obter o lock exclusivo imediatamente (não-bloqueante: -n)
if ! flock -n 200; then
echo "[AVISO] Uma instância deste script já está em execução. Abortando." >&2
exit 1
fi
O Linux libera esse lock automaticamente assim que o processo do script é finalizado pelo kernel.
4. Template Completo de Backup com Rotação de 7 Dias
Abaixo está um script completo combinando todas as boas práticas:
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'
# ==========================================
# CONFIGURAÇÕES
# ==========================================
BACKUP_SRC="/var/www/html"
BACKUP_DEST="/backups/app"
RETENTION_DAYS=7
LOG_FILE="/var/log/backup_app.log"
LOCK_FILE="/var/lock/backup_app.lock"
# Redireciona stdout e stderr para o log e para o terminal simultaneamente
exec > >(tee -a "$LOG_FILE") 2>&1
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"
}
# ==========================================
# TRAVAS E LIMPEZA
# ==========================================
exec 200>"$LOCK_FILE"
if ! flock -n 200; then
log "[AVISO] Backup já em andamento. Encerrando."
exit 0
fi
trap 'log "[ERRO] Script interrompido na linha $LINENO ($BASH_COMMAND)"' ERR
# ==========================================
# EXECUÇÃO DO BACKUP
# ==========================================
log "Iniciando rotina de backup..."
mkdir -p "$BACKUP_DEST"
DATE=$(date '+%Y%m%d_%H%M%S')
ARCHIVE_NAME="backup_${DATE}.tar.gz"
log "Compactando $BACKUP_SRC -> $BACKUP_DEST/$ARCHIVE_NAME..."
tar -czf "$BACKUP_DEST/$ARCHIVE_NAME" -C "$BACKUP_SRC" .
# Rotação: Remove arquivos com mais de 7 dias
log "Removendo backups com mais de $RETENTION_DAYS dias..."
find "$BACKUP_DEST" -name "backup_*.tar.gz" -type f -mtime +"$RETENTION_DAYS" -delete
log "Backup concluído com sucesso! Tamanho: $(du -sh "$BACKUP_DEST/$ARCHIVE_NAME" | cut -f1)"
Vocês costumam usar set -euo pipefail como padrão nos scripts de vocês ou preferem tratar erros manualmente em cada comando?
Guia detalhado com dezenas de exemplos de automação em Shell: tecmestre.com.br/guia-pratico-shell-script-linux-exemplos/