Script em Bash: Um Mergulho Profundo em Códigos de Saída e Status
Entenda códigos de saída do Bash, inspecione $? com segurança, defina status com exit e construa um fluxo de controle confiável.
Script em Bash: Um Mergulho Profundo em Códigos de Saída e Status
Os códigos de saída do Bash são como os comandos informam ao seu script o que aconteceu. 0 significa sucesso, e um status diferente de zero indica que o comando falhou ou produziu um resultado que seu script precisa tratar.
Este guia mostra como ler $?, definir status com exit e usar códigos de saída para construir um fluxo de controle mais seguro na automação com Bash.
Entendendo os Códigos de Saída
Todo comando, função ou script executado no Bash retorna um código de saída ao finalizar. Este é um valor inteiro que sinaliza o resultado da execução. Por convenção:
0(Zero): Indica sucesso. O comando foi concluído sem erros.Diferente de zero(Qualquer outro inteiro): Indica falha ou um erro. Valores diferentes de zero podem, às vezes, significar tipos específicos de erros.
Esta simples convenção de 0 vs. diferente de zero é fundamental para como o Bash opera e como você pode construir lógica condicional em seus scripts.
Recuperando o Último Código de Saída: $?
O Bash fornece um parâmetro especial, $?, que contém o código de saída do comando em primeiro plano executado mais recentemente. Você pode verificar seu valor imediatamente após qualquer comando para determinar seu resultado.
# Exemplo 1: Comando bem-sucedido
ls /tmp
echo "Código de saída para 'ls /tmp': $?"
# Exemplo 2: Comando falhou (diretório inexistente)
ls /diretorio_inexistente
echo "Código de saída para 'ls /diretorio_inexistente': $?"
# Exemplo 3: Grep encontrando uma correspondência (sucesso)
grep "root" /etc/passwd
echo "Código de saída para 'grep root /etc/passwd': $?"
# Exemplo 4: Grep não encontrando correspondência (falha, mas esperada)
grep "usuario_inexistente" /etc/passwd
echo "Código de saída para 'grep usuario_inexistente /etc/passwd': $?"
Saída (pode variar ligeiramente dependendo do seu sistema e do conteúdo de /etc/passwd):
ls /tmp
# ... (lista de arquivos em /tmp)
Código de saída para 'ls /tmp': 0
ls /diretorio_inexistente
ls: cannot access '/diretorio_inexistente': No such file or directory
Código de saída para 'ls /diretorio_inexistente': 2
grep "root" /etc/passwd
root:x:0:0:root:/root:/bin/bash
Código de saída para 'grep root /etc/passwd': 0
grep "usuario_inexistente" /etc/passwd
Código de saída para 'grep usuario_inexistente /etc/passwd': 1
Observe que grep retorna 0 para uma correspondência e 1 para nenhuma correspondência. Ambos são resultados válidos no contexto do grep, mas para lógica condicional, 0 significa o encontro bem-sucedido do padrão.
Definindo Códigos de Saída Explicitamente com exit
Ao escrever seus próprios scripts ou funções, você pode definir explicitamente seu código de saída usando o comando exit seguido por um valor inteiro. Isso é crucial para comunicar o resultado do script para processos chamadores, scripts pai ou pipelines de CI/CD.
#!/bin/bash
# script_sucesso.sh
echo "Este script será encerrado com sucesso (0)"
exit 0
#!/bin/bash
# script_falha.sh
echo "Este script será encerrado com falha (1)"
exit 1
# Teste os scripts
./script_sucesso.sh
echo "Status de script_sucesso.sh: $?"
./script_falha.sh
echo "Status de script_falha.sh: $?"
Saída:
Este script será encerrado com sucesso (0)
Status de script_sucesso.sh: 0
Este script será encerrado com falha (1)
Status de script_falha.sh: 1
Dica: Se
exitfor chamado sem argumento, o status de saída do script será o status de saída do último comando executado antes deexitser chamado.
Aproveitando os Códigos de Saída para o Fluxo de Controle
Os códigos de saída são a espinha dorsal da execução condicional no Bash, permitindo criar scripts dinâmicos e responsivos.
Declarações Condicionais (if/else)
A declaração if no Bash avalia o código de saída de um comando. Se o comando sair com 0 (sucesso), o bloco if é executado. Caso contrário, o bloco else (se presente) é executado.
#!/bin/bash
ARQUIVO="/caminho/para/meu/arquivo_importante.txt"
if [ -f "$ARQUIVO" ]; then # O comando de teste `[` sai com 0 se o arquivo existir
echo "Arquivo '$ARQUIVO' existe. Prosseguindo com o processamento..."
# Adicione a lógica de processamento do arquivo aqui
# Exemplo: cat "$ARQUIVO"
exit 0
else
echo "Erro: Arquivo '$ARQUIVO' não existe."
echo "Abortando script."
exit 1
fi
Operadores Lógicos (&&, ||)
O Bash fornece poderosos operadores lógicos de curto-circuito que dependem dos códigos de saída:
comando1 && comando2:comando2é executado apenas secomando1sair com0(sucesso).comando1 || comando2:comando2é executado apenas secomando1sair com um valordiferente de zero(falha).
Estes são extremamente úteis para comandos sequenciais e mecanismos de fallback.
#!/bin/bash
DIR_LOG="/var/log/minha_app"
# Criar diretório apenas se não existir
mkdir -p "$DIR_LOG" && echo "Diretório de log '$DIR_LOG' garantido."
# Tentar iniciar um serviço, se falhar, tentar um comando alternativo
systemctl start meu_servico || { echo "Falha ao iniciar meu_servico. Tentando fallback..."; ./iniciar_fallback.sh; }
# Um comando que deve ter sucesso para o script continuar
copiar_dados_para_local_backup && echo "Backup de dados bem-sucedido." || { echo "Falha no backup de dados!"; exit 1; }
echo "Script concluído com sucesso."
exit 0
set -e: Sair ao Erro
A opção set -e é uma ferramenta poderosa para tornar seus scripts mais robustos. Quando set -e está ativo, o Bash sairá imediatamente do script se qualquer comando sair com um status diferente de zero. Isso evita falhas silenciosas e erros em cascata.
#!/bin/bash
set -e # Sair imediatamente se um comando sair com status diferente de zero
echo "Iniciando script..."
# Este comando será bem-sucedido
ls /tmp
echo "Primeiro comando bem-sucedido."
# Este comando falhará e, por causa do 'set -e', o script sairá aqui
ls /caminho_inexistente
echo "Esta linha nunca será alcançada se o comando anterior falhar."
exit 0 # Esta linha só será alcançada se todos os comandos anteriores forem bem-sucedidos
Saída (se /caminho_inexistente não existir):
Iniciando script...
# ... (saída de ls /tmp)
Primeiro comando bem-sucedido.
ls: cannot access '/caminho_inexistente': No such file or directory
O script termina após o comando ls falhar, e a mensagem "Esta linha nunca será alcançada" não é impressa.
Aviso:
set -etem exceções, e alguns comandos retornam legitimamente diferente de zero para resultados esperados. Por exemplo,grepretorna1quando não encontra correspondência. Prefira umif grep -q "padrão" arquivo; then ... fiexplícito quando você se importa com o resultado.
Cenários Comuns de Códigos de Saída e Melhores Práticas
Embora 0 para sucesso e diferente de zero para falha seja a regra geral, alguns códigos diferentes de zero têm significados comuns, especialmente para comandos do sistema e built-ins:
0: Sucesso.1: Erro geral, captura tudo para problemas diversos.2: Uso incorreto de built-ins do shell ou argumentos de comando incorretos.126: Comando invocado não pode ser executado (por exemplo, problema de permissão, não é um executável).127: Comando não encontrado (por exemplo, erro de digitação no nome do comando, não está noPATH).128 + N: O comando foi terminado pelo sinalN. Por exemplo,130(128 + 2) significa que o comando foi terminado porSIGINT(Ctrl+C).
Ao criar seus próprios scripts, mantenha 0 para sucesso. Para falhas, 1 é um padrão seguro para um erro geral. Se seu script lida com múltiplas condições de erro distintas, você pode usar valores mais altos diferentes de zero (por exemplo, 10, 20, 30) para diferenciá-los, mas documente esses códigos personalizados claramente.
Melhores Práticas para Scripts Robutos:
- Sempre Verifique Comandos Críticos: Não presuma sucesso. Use declarações
ifou&¶ verificar etapas críticas. - Forneça Mensagens de Erro Informativas: Quando um script falhar, imprima mensagens claras para
stderrexplicando o que deu errado e como potencialmente corrigir. Use>&2para redirecionar a saída para o erro padrão.meu_comando || { echo "Erro: meu_comando falhou. Verifique os logs." >&2; exit 1; } - Limpeza em Caso de Falha: Use
trappara garantir que arquivos temporários ou recursos sejam limpos mesmo se o script sair prematuramente.limpar() { echo "Limpando arquivos temporários..." rm -f /tmp/meu_arquivo_temp_$$ } trap limpar EXIT - Valide Entradas: Verifique argumentos do script ou variáveis de ambiente no início e saia com um erro informativo se forem inválidos.
- Registre o Status de Saída: Para automação complexa, registre o status de saída das operações principais para auditoria e depuração.
Exemplo do Mundo Real: Um Trecho de Script de Backup Robusto
Aqui está como você pode combinar esses conceitos em um cenário prático:
#!/bin/bash
set -e # Sair imediatamente se um comando sair com status diferente de zero
ORIGEM_BACKUP="/data/app/config"
DESTINO_BACKUP="/mnt/backup/configs"
TIMESTAMP=$(date +%Y%m%d%H%M%S)
ARQUIVO_LOG="/var/log/backup_config_${TIMESTAMP}.log"
# --- Funções ---
log_message() {
echo "$(date +%Y-%m-%d_%H:%M:%S) - $1" | tee -a "$ARQUIVO_LOG"
}
limpar() {
log_message "Limpeza iniciada."
if [ -n "${DIR_TEMP:-}" ] && [ -d "$DIR_TEMP" ]; then
rm -rf "$DIR_TEMP"
log_message "Diretório temporário removido: $DIR_TEMP"
fi
}
# --- Trap para saída e sinais ---
trap 'limpar' EXIT
trap 'log_message "Script interrompido (SIGINT). Saindo."; exit 130' INT
trap 'log_message "Script terminado (SIGTERM). Saindo."; exit 143' TERM
# --- Lógica Principal do Script ---
log_message "Iniciando backup de configuração."
# 1. Verificar se o diretório de origem existe
if [ ! -d "$ORIGEM_BACKUP" ]; then
log_message "Erro: Origem do backup '$ORIGEM_BACKUP' não existe." >&2
exit 2 # Código de erro personalizado para origem inválida
fi
# 2. Garantir que o destino do backup exista
mkdir -p "$DESTINO_BACKUP" || {
log_message "Erro: Falha ao criar/garantir destino do backup '$DESTINO_BACKUP'." >&2
exit 3 # Código de erro personalizado para problema no destino
}
# 3. Criar um diretório temporário para compressão
DIR_TEMP=$(mktemp -d)
log_message "Diretório temporário criado: $DIR_TEMP"
# 4. Copiar dados para o diretório temporário
cp -r "$ORIGEM_BACKUP" "$DIR_TEMP/" || {
log_message "Erro: Falha ao copiar dados de '$ORIGEM_BACKUP' para '$DIR_TEMP'." >&2
exit 4 # Código de erro personalizado para falha na cópia
}
log_message "Dados copiados para local temporário."
# 5. Comprimir os dados
NOME_ARQUIVO="config_backup_${TIMESTAMP}.tar.gz"
tar -czf "$DIR_TEMP/$NOME_ARQUIVO" -C "$DIR_TEMP" "$(basename "$ORIGEM_BACKUP")" || {
log_message "Erro: Falha ao comprimir dados." >&2
exit 5 # Código de erro personalizado para falha na compressão
}
log_message "Dados comprimidos em $NOME_ARQUIVO."
# 6. Mover o arquivo para o destino final
mv "$DIR_TEMP/$NOME_ARQUIVO" "$DESTINO_BACKUP/" || {
log_message "Erro: Falha ao mover arquivo para '$DESTINO_BACKUP'." >&2
exit 6 # Código de erro personalizado para falha na movimentação
}
log_message "Arquivo movido para '$DESTINO_BACKUP/$NOME_ARQUIVO'."
log_message "Backup concluído com sucesso!"
exit 0
Conclusão
Trate os códigos de saída como parte da interface do seu script. Verifique comandos críticos, retorne status diferentes de zero claros em caso de falha e documente quaisquer códigos personalizados que outro script ou trabalho de CI possa precisar interpretar.