1

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 $DIRETORIO está 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 || true ou envolva em um if.
  • -u (nounset): Trata variáveis não inicializadas como erro fatal. Se você digitar $USER_NANE em 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. Com pipefail, se o cmd1 falhar, 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/

Carregando publicação patrocinada...