Estratégias Eficazes de Tratamento de Erros em Scripts Bash
Use modo estrito, traps, códigos de saída e mensagens claras em stderr para fazer scripts Bash falharem com segurança e se limparem sozinhos.
Estratégias Eficazes de Tratamento de Erros em Scripts Bash
O tratamento de erros em scripts Bash é importante porque um script que falha silenciosamente pode copiar arquivos parciais, implantar código quebrado ou excluir o caminho errado. Você quer que seu script pare quando uma etapa crítica falha, explique o que aconteceu e limpe arquivos temporários antes de sair.
Os padrões abaixo cobrem as peças que você mais precisa: modo estrito, verificações explícitas, trap e relatórios de erro simples.
A Base: Entendendo o Status de Saída
No mundo Unix, todo comando executado retorna um status de saída (ou código de saída), um valor inteiro que indica o resultado de sua operação. Esse status é imediatamente armazenado na variável especial $?.
- Código de Saída 0: Por convenção, isso significa sucesso (ou 'verdadeiro').
- Códigos de Saída 1–255: Isso significa falha (ou 'falso'). Códigos específicos geralmente se relacionam a tipos específicos de falha (por exemplo, 1 para erros gerais, 127 para comando não encontrado).
Scripts confiáveis devem verificar o status de saída de comandos críticos e retornar um código significativo diferente de zero se o script falhar.
Estratégia Central 1: A Tríade de Scripts Defensivos
Para qualquer script de automação sério, você deve começar aplicando três opções fundamentais imediatamente após a linha shebang (#!/bin/bash). Essas opções impõem um comportamento estrito e previsível.
1. Saída Imediata em Caso de Falha (set -e)
A opção set -e (ou set -o errexit) determina que o script deve sair imediatamente se qualquer comando falhar (retornar um status de saída diferente de zero).
Isso é frequentemente chamado de princípio "falhe rápido" e impede que o script prossiga com ações potencialmente destrutivas usando resultados de pré-requisitos incompletos ou com falha.
#!/bin/bash
set -e
echo "Iniciando processo..."
mkdir /tmp/test_dir
cp non_existent_file /tmp/test_dir/ # Este comando falha (código de saída > 0)
echo "Esta linha não será executada." # Script sai aqui
Aviso: Ressalvas do
set -e
set -enão aciona uma saída em vários contextos comuns, incluindo comandos testados porifouwhile, comandos na maioria das listas&&ou||, e comandos cujo status é invertido com!. Trate-o como uma rede de segurança, não como um substituto para verificações claras em torno de falhas esperadas.
2. Tratar Variáveis Não Definidas como Erros (set -u)
A opção set -u (ou set -o nounset) garante que o script trate o uso de qualquer variável não definida como um erro, fazendo com que o script saia imediatamente (semelhante ao set -e). Isso evita bugs sutis onde um erro de digitação em um nome de variável leva a uma string vazia sendo passada para um comando crítico.
#!/bin/bash
set -u
# echo "A variável é: $UNDEFINED_VAR" # Script falha e sai aqui
MY_VAR="definido"
echo "A variável é: ${MY_VAR}"
3. Lidando com Pipelines de Comandos (set -o pipefail)
Por padrão, um pipeline de comandos (comando1 | comando2 | comando3) apenas relata o status de saída do último comando (comando3). Se comando1 falhar, mas comando3 for bem-sucedido, $? será 0, mascarando a falha.
set -o pipefail muda esse comportamento, garantindo que o pipeline retorne um status diferente de zero se qualquer comando no pipeline falhar. Isso é crucial para processamento de dados confiável.
#!/bin/bash
set -o pipefail
# O comando `false` sempre sai com 1
# Sem pipefail, esta linha retornaria 0 porque `cat` é bem-sucedido.
false | cat # Retorna 1 por causa do pipefail
if [ $? -ne 0 ]; then
echo "Pipeline falhou."
fi
Melhor Prática: O Cabeçalho
Sempre inicie scripts robustos com as opções defensivas combinadas:
#!/bin/bash set -euo pipefail
Estratégia Central 2: Verificações Manuais e Execução Condicional
Embora set -e lide com a maioria das falhas, muitas vezes você precisa verificar o status do comando manualmente, particularmente quando a falha é esperada ou precisa de registro específico.
A Verificação com if
A maneira padrão de verificar o sucesso de um comando é capturando seu status de saída dentro de um bloco if. Este método substitui o comportamento de set -e, permitindo que você lide explicitamente com o erro.
#!/bin/bash
set -euo pipefail
TEMP_FILE="/tmp/data_processing_$$/config.dat"
# Tentar criar o diretório; lidar com a falha explicitamente
if ! mkdir -p "$(dirname "$TEMP_FILE")"; then
echo "[ERRO] Não foi possível criar o diretório temporário." >&2
exit 1
fi
# Tentar buscar dados
if ! curl -sSf https://api.example.com/data > "$TEMP_FILE"; then
echo "[ERRO] Falha ao buscar dados da API." >&2
exit 2
fi
echo "Dados recuperados com sucesso."
Dica: As flags
-sSfparacurl(silencioso, falha, mostrar erros) forçamcurla retornar um código de saída diferente de zero em erros HTTP, facilitando o tratamento de erros.
Usando Operadores de Curto-Circuito (&& e ||)
Esses operadores lógicos fornecem maneiras concisas de encadear comandos com base no sucesso (&&) ou falha (||).
comando1 && comando2: Executacomando2apenas secomando1for bem-sucedido.comando1 || comando2: Executacomando2apenas secomando1falhar.
# Exemplo: Criar diretório E copiar arquivo, falhar se qualquer etapa falhar
mkdir logs && cp /var/log/syslog logs/system.log
# Exemplo: Tentar backup, OU registrar erro e sair se o backup falhar
pg_dump database > backup.sql || { echo "Backup falhou!" >&2; exit 10; }
Estratégia Avançada 3: Limpeza Garantida com trap
Quando um script lida com arquivos temporários, arquivos de bloqueio ou conexões de rede estabelecidas, saídas abruptas (bem-sucedidas ou devido a erro) podem deixar o sistema em um estado inconsistente. O comando trap permite que você defina um comando ou função a ser executada quando o script receber um sinal específico.
O Sinal EXIT
O sinal EXIT é o mais útil para limpeza geral. O comando capturado é executado sempre que o script sai, independentemente de a saída ter sido bem-sucedida, uma chamada exit manual ou uma saída acionada por set -e.
#!/bin/bash
TEMP_DIR=$(mktemp -d)
# Definição da função de limpeza
cleanup() {
EXIT_CODE=$?
echo "Limpando diretório temporário: ${TEMP_DIR}"
rm -rf "$TEMP_DIR"
# Se o script saiu devido a falha, restaurar o código de falha
if [ $EXIT_CODE -ne 0 ]; then
exit $EXIT_CODE
fi
}
# Configurar o trap: Executar a função 'cleanup' na saída do script
trap cleanup EXIT
# --- Lógica Principal do Script ---
echo "Processando dados em ${TEMP_DIR}"
# Simular uma operação bem-sucedida...
# ... script continua ...
# Simular uma falha crítica que aciona set -e
false
# Esta linha é inalcançável, mas a limpeza ainda é garantida.
echo "Concluído."
Lidando com Sinais Específicos (TERM, INT)
Você também pode capturar sinais de término específicos como TERM (solicitação de término) ou INT (interrupção, geralmente Ctrl+C) para garantir um desligamento gracioso quando um usuário ou agendador cancela o trabalho.
trap 'echo "Script interrompido pelo usuário (Ctrl+C). Abortando limpeza." >&2; exit 130' INT
Estratégia 4: Relatórios de Erro e Registro Personalizados
Um script profissional deve usar uma função de erro dedicada para centralizar os relatórios, garantindo consistência e canais de saída adequados.
Redirecionando Erros para o Erro Padrão (>&2)
Mensagens de erro devem sempre ser impressas no Erro Padrão (stderr ou descritor de arquivo 2), permitindo que a Saída Padrão (stdout ou descritor de arquivo 1) permaneça limpa para dados ou resultados bem-sucedidos.
O Padrão da Função die
Crie uma função, frequentemente chamada die ou error_exit, que lida com o registro da mensagem, limpeza (se traps não forem usados) e saída com um código especificado.
# Função para imprimir mensagem de erro e sair
die() {
local msg=$1
local code=${2:-1}
echo "$(date +'%Y-%m-%d %H:%M:%S') [FATAL]: ${msg}" >&2
exit "$code"
}
# Exemplo de Uso:
REQUIRED_VAR="$1"
if [ -z "$REQUIRED_VAR" ]; then
die "Argumento obrigatório ausente (Nome do Banco de Dados)." 3
fi
# ... mais adiante no script ...
if ! validate_checksum "$FILE"; then
die "Falha na verificação de checksum para $FILE." 5
fi
Torne as Falhas Chatas
Para tratamento de erros confiável em scripts Bash, inicie cada script não trivial com set -euo pipefail, use if ! comando; then ...; fi onde você espera que um comando possa falhar e envie erros para stderr. Se seu script criar arquivos temporários, arquivos de bloqueio ou saída parcial, adicione trap cleanup EXIT antes do trabalho arriscado começar.
Essa combinação mantém pequenas tarefas de automação previsíveis e torna as falhas de produção mais fáceis de diagnosticar.