Tratamento de erros em scripts: set -e, set -u, set -o pipefail

1. Introdução ao tratamento de erros em Bash

O comportamento padrão do Bash é notoriamente permissivo quando se trata de erros. Por padrão, um script continua executando mesmo quando um comando falha, ignorando silenciosamente códigos de saída diferentes de zero. Isso significa que um erro em uma etapa intermediária pode passar despercebido, corrompendo dados, gerando saídas incompletas ou causando falhas catastróficas em momentos inesperados.

Considere um script de backup que tenta copiar arquivos, compactá-los e enviá-los para um servidor remoto. Se a compactação falhar, o script continua e envia um arquivo corrompido. Sem tratamento de erros, essas falhas se propagam silenciosamente.

As opções set -e, set -u e set -o pipefail oferecem um controle granular sobre como o Bash reage a erros, transformando scripts frágeis em programas robustos e previsíveis.

2. set -e: Abortando na primeira falha

A opção set -e instrui o Bash a interromper imediatamente a execução do script quando qualquer comando retornar um código de saída diferente de zero. Isso evita que erros se propaguem.

#!/bin/bash
set -e

echo "Criando diretório..."
mkdir /tmp/meu_diretorio

echo "Copiando arquivos..."
cp /etc/passwd /tmp/meu_diretorio/

echo "Removendo diretório..."
rm -rf /tmp/meu_diretorio_inexistente  # Isso interrompe o script

echo "Esta linha nunca será executada"

Neste exemplo, rm -rf em um diretório inexistente retorna código 1, fazendo o script abortar antes da última mensagem.

Exceções importantes: set -e não afeta comandos dentro de condicionais (if, while, until) ou em listas &&/||. Isso permite usar comandos que podem falhar como condições lógicas:

#!/bin/bash
set -e

if grep -q "root" /etc/passwd; then
    echo "Usuário root encontrado"
fi

# O comando grep acima pode falhar sem interromper o script

Armadilhas comuns: Comandos como grep que não encontram padrões retornam código 1. Em set -e, isso interrompe o script se não estiver dentro de um condicional. Da mesma forma, rm em arquivo inexistente ou cd em diretório que não existe são causas frequentes de abortos inesperados.

3. set -u: Detectando variáveis não definidas

Variáveis não definidas são uma fonte comum de bugs em scripts Bash. Por padrão, o Bash expande variáveis não definidas para strings vazias, mascarando erros de digitação ou lógica.

#!/bin/bash
set -u

nome="João"
echo "Olá, $nome"  # Funciona
echo "Olá, $noma"  # Erro: variável não definida, script aborta

Com set -u, qualquer tentativa de expandir uma variável não definida resulta em erro fatal. Isso é particularmente útil para detectar:

  • Erros de digitação em nomes de variáveis
  • Parâmetros de função não fornecidos
  • Variáveis de ambiente ausentes

Cuidados com $* e $@: Em scripts que recebem argumentos, set -u pode causar erros se você tentar acessar $1 sem verificar se o argumento foi fornecido. A solução é usar ${1:-valor_default} para fornecer valores padrão.

Interação com arrays: Arrays vazios também são afetados. Se você declarar um array e tentar acessar seu primeiro elemento sem verificá-lo, set -u pode interromper o script.

4. set -o pipefail: Falhas em pipelines

O comportamento padrão do Bash em pipelines é considerar apenas o código de saída do último comando. Isso significa que um erro no meio do pipeline pode passar despercebido:

#!/bin/bash
# Comportamento padrão
comando_que_falha | comando_ok
echo $?  # Mostra 0 (sucesso), mesmo se o primeiro comando falhou

set -o pipefail altera esse comportamento: o pipeline retorna o código de saída do último comando que falhou (ou zero se todos tiverem sucesso).

#!/bin/bash
set -o pipefail

cat /etc/arquivo_inexistente | grep "root" | sort
echo "Código de saída: $?"  # Mostra o erro do cat, não do sort

Exemplo prático com pipeline de três estágios:

#!/bin/bash
set -o pipefail

# Simulação: comando2 falha
comando1() { echo "dado1"; }
comando2() { return 1; }
comando3() { cat; }

comando1 | comando2 | comando3
echo "Pipeline falhou com código: $?"  # Mostra 1 (erro do comando2)

5. Combinando set -e, set -u e set -o pipefail

A combinação das três opções forma a tríade de segurança para scripts Bash modernos. O idiom set -euo pipefail é amplamente adotado como padrão em scripts profissionais.

#!/bin/bash
set -euo pipefail

# Script robusto de processamento de dados
DIR_DADOS="/tmp/dados"
ARQUIVO_SAIDA="/tmp/resultado.txt"

echo "Verificando diretório de dados..."
[ -d "$DIR_DADOS" ] || { echo "Erro: diretório $DIR_DADOS não existe"; exit 1; }

echo "Processando arquivos..."
for arquivo in "$DIR_DADOS"/*.txt; do
    [ -f "$arquivo" ] || continue  # Proteção contra glob sem match

    echo "Processando: $arquivo"
    cat "$arquivo" | grep -v "^#" | sort -u >> "$ARQUIVO_SAIDA"
done

echo "Compactando resultado..."
gzip "$ARQUIVO_SAIDA"

echo "Script concluído com sucesso"

Ordem de avaliação: As opções são aplicadas na ordem em que são encontradas, mas é recomendável declará-las juntas no início do script. A combinação euo funciona porque:
- -e interrompe em qualquer falha
- -u detecta variáveis não definidas
- -o pipefail garante que falhas em pipelines sejam propagadas

6. Limitações e boas práticas complementares

Limitações do set -e:
- Subshells: (comando_que_falha) dentro de parênteses não interrompe o script pai
- eval: Comandos avaliados dinamicamente podem escapar do controle
- Comandos built-in: source com arquivo inexistente não é capturado

Uso de trap com ERR: Para ações personalizadas em falhas:

#!/bin/bash
set -euo pipefail

trap 'echo "Erro na linha $LINENO"; exit 1' ERR

echo "Executando tarefa perigosa..."
rm -rf /diretorio_importante  # Se falhar, trap é acionado

Desabilitando temporariamente: Use set +e e set +u para seções específicas:

#!/bin/bash
set -euo pipefail

# Seção onde falhas são aceitáveis
set +e
grep "padrao" /arquivo/opcional || true
set -e

# Script continua normalmente

7. Debugging com set -x e validação final

set -x (ou set -o xtrace) é diferente das opções de tratamento de erros. Enquanto -e, -u e -o pipefail controlam quando o script para, -x controla o que é exibido durante a execução.

#!/bin/bash
set -euo pipefail
set -x  # Ativa modo de depuração

echo "Depurando script..."
variavel="teste"
echo "$variavel"

set +x  # Desativa depuração
echo "Modo normal"

Checklist para validar robustez de scripts:

  1. set -euo pipefail está no início do script?
  2. Comandos que podem falhar legitimamente estão dentro de condicionais?
  3. Variáveis têm valores padrão quando necessário (${var:-default})?
  4. trap ERR está configurado para logging ou ações de limpeza?
  5. O script foi testado com entradas inválidas e situações de erro?
  6. Há verificações explícitas para arquivos e diretórios antes de usá-los?
  7. set +e é usado apenas em blocos específicos e com || true quando apropriado?

A combinação set -euo pipefail não é uma bala de prata, mas estabelece uma base sólida para scripts confiáveis. Combinada com boas práticas de validação e tratamento de erros, ela transforma scripts Bash de utilitários frágeis em ferramentas robustas adequadas para automação de produção.

Referências