Entendendo Códigos de Saída: Tratamento Eficaz de Erros com $? e exit

Use códigos de saída do Bash, $?, exit, set -e e pipefail para tornar as falhas de script claras e controladas.

Entendendo Códigos de Saída: Tratamento Eficaz de Erros com $? e exit

Quando um script Bash falha, o código de saída informa ao chamador o que fazer a seguir: continuar, tentar novamente, alertar ou parar. Entender códigos de saída, $? e exit é a diferença entre automação que esconde falhas e automação que as relata claramente.

Este guia mostra como o Bash rastreia o status dos comandos e como você pode usar esse status para um tratamento de erros simples e confiável.

O Conceito de Status de Saída

Todo comando ou programa executado em um ambiente shell semelhante ao Unix—seja um comando embutido como cd, um utilitário externo como grep ou outro script shell—retorna um valor inteiro ao ser concluído. Esse inteiro é o código de saída, que sinaliza o resultado da operação para o processo chamador.

A Convenção Padrão

A convenção para códigos de saída é universalmente reconhecida:

  • 0 (Zero): Significa sucesso. O comando foi executado exatamente como esperado e nenhum erro ocorreu.
  • 1 a 255: Significam falha ou condições de erro específicas. Esses valores diferentes de zero indicam que algo deu errado. Números mais altos geralmente correspondem a tipos específicos de erros (por exemplo, arquivo não encontrado, permissão negada, erro de sintaxe), embora o significado exato dependa do programa específico.

Nota sobre o Intervalo: Embora os códigos de saída sejam tecnicamente um valor de 8 bits (0-255), scripts shell geralmente se preocupam apenas com 0 para sucesso e diferente de zero para falha. Códigos de saída maiores que 255 são geralmente truncados ou interpretados módulo 256 pelo shell.

Inspecionando o Último Código de Saída: A Variável $?

A variável shell especial $? (dólar ponto de interrogação) é central para monitorar o status do comando. Imediatamente após qualquer comando ser executado, o shell armazena seu código de saída em $?.

Como Usar $?

Você deve verificar $? imediatamente após o comando de seu interesse, pois qualquer comando subsequente (até mesmo ecoar a variável) sobrescreverá seu valor.

Exemplo 1: Verificando Sucesso e Falha

# 1. Um comando bem-sucedido
echo "Teste de sucesso" > /dev/null
echo "Código de saída para sucesso: $?"

# 2. Um comando com falha (por exemplo, tentar listar um arquivo inexistente)
ls /caminho/inexistente
echo "Código de saída para falha: $?"

Saída Esperada:

Código de saída para sucesso: 0
ls: não é possível acessar '/caminho/inexistente': Arquivo ou diretório inexistente
Código de saída para falha: 2

Implementando Verificação Condicional de Erros

Simplesmente saber o código de saída não é suficiente; o poder vem de usar essas informações para controlar o fluxo do script. Isso é tipicamente feito usando instruções if ou operadores de curto-circuito (&& e ||).

Usando Instruções if

Esta é a maneira mais explícita de lidar com erros:

if grep -q "dados importantes" arquivo_log.txt;
then
    echo "Dados encontrados com sucesso."
else
    ULTIMO_STATUS=$?
    echo "Erro: Grep falhou com status $ULTIMO_STATUS. Dados não encontrados."
    # Considere sair aqui se o script não puder prosseguir
fi

No exemplo acima, grep -q suprime a saída (-q) e retorna 0 apenas se uma correspondência for encontrada. A estrutura if verifica o status de saída automaticamente, mas capturar explicitamente $? dentro do bloco else é útil para registro detalhado.

Usando Lógica de Curto-Circuito (&& e ||)

Para verificações sequenciais simples, os operadores de curto-circuito fornecem tratamento de erros conciso:

  • && (E): O comando após && só é executado se o comando anterior foi bem-sucedido (retornou 0).
  • || (OU): O comando após || só é executado se o comando anterior falhou (retornou diferente de zero).

Exemplo 2: Tratamento de Erros Conciso

# 1. Só executa 'processar_dados' SE 'buscar_dados' for bem-sucedido
buscar_dados.sh && ./processar_dados.sh

# 2. Executa 'enviar_alerta' SOMENTE SE a operação principal falhar
rsync -a origem/ destino/ || echo "RSync falhou em $(date)" >> /var/log/erros_rsync.log

Controlando a Terminação do Script com exit

O comando exit é usado para encerrar imediatamente o script shell ou função atual e retornar um status de saída especificado para o chamador (que pode ser outro script ou o terminal do usuário).

Sintaxe e Uso

A sintaxe é simplesmente exit [codigo_status].

Se nenhum status for fornecido, exit assume como padrão o status do comando em primeiro plano executado mais recentemente. Se você chamar explicitamente exit 0 sem executar nenhum comando primeiro, ele retorna 0.

Exemplo 3: Saindo por Falha de Pré-Condição

Este script garante que um arquivo de configuração necessário exista antes de prosseguir.

ARQUIVO_CONFIG="/etc/app/config.conf"

if [[ ! -f "$ARQUIVO_CONFIG" ]]; then
    echo "Erro: Arquivo de configuração não encontrado em $ARQUIVO_CONFIG."
    # Encerra o script imediatamente com um código de erro específico (por exemplo, 20)
    exit 20 
fi

echo "Configuração carregada. Continuando o script..."
# ... resto do script
exit 0

Melhor Prática: Usando Códigos de Saída Significativos

Embora 0 e 1 cubram a maioria dos casos básicos, usar diferentes códigos diferentes de zero ajuda o script chamador a diagnosticar o problema exato:

Código Significado (Exemplo)
0 Sucesso
1 Erro geral abrangente
2-10 Erros de sintaxe, problemas de análise de argumentos
20 Pré-requisito ausente (por exemplo, arquivo não encontrado)
30 Problema de permissão

Fazendo Scripts Falharem Rapidamente: O Comando set

Para máxima confiabilidade em scripts complexos, é uma boa prática forte habilitar a verificação de erros globalmente usando as opções do comando set no topo do seu script:

#!/bin/bash

# Sai imediatamente se um comando sair com um status diferente de zero.
set -e

# Trata variáveis não definidas como um erro ao substituir.
set -u

# Pipefail: Garante que o status de retorno de um pipeline seja o status do comando mais à direita que saiu com um status diferente de zero.
set -o pipefail

# (Opcional, mas útil) Imprime comandos à medida que são executados para depuração
# set -x 

# Se algum comando abaixo falhar, o script para imediatamente.
ls /caminho/valido && grep padrao arquivo.txt && ./proximo_passo.sh

# A linha a seguir será executada SOMENTE se todos os comandos anteriores forem bem-sucedidos.
echo "Todos os passos concluídos."

Quando set -e está ativo, muitos status diferentes de zero não tratados param o script antes que comandos posteriores sejam executados com suposições incorretas. Ele tem exceções em condicionais, pipelines e comandos compostos, portanto, ainda trate falhas esperadas explicitamente.

Por exemplo, grep retorna 1 quando não encontra correspondência. Isso pode ser um resultado normal, não um erro fatal:

if grep -q "PRONTO" status.txt; then
    echo "O serviço está pronto."
else
    echo "O serviço ainda não está pronto."
fi

Conclusão

Verifique comandos críticos onde eles são executados, escreva erros em stderr e saia com um status diferente de zero quando o script não puder continuar com segurança. Use set -euo pipefail para scripts de falha rápida, mas não confie nele como sua única estratégia de tratamento de erros.