Em scripts Bash, todo comando retorna um código de saída (exit code) quando termina. Esse código é um número inteiro entre 0 e 255 que indica se o comando foi bem-sucedido ou não. Entender e utilizar códigos de saída é fundamental para controlar o fluxo do script, depurar erros e criar programas robustos. Nesta aula, veremos como acessar o código do último comando com $?, como definir o código de saída de um script com exit, as convenções adotadas e como encadear comandos baseados no status.

Dominar esses conceitos permite criar scripts que reagem adequadamente a falhas, evitam execuções desnecessárias e se integram bem com outros programas e pipelines.

$?

A variável especial $? armazena o código de saída do último comando executado no shell. Após cada comando, $? é atualizado automaticamente. Você pode usá-lo para verificar se um comando teve sucesso ou falhou, e tomar decisões condicionais.

Por exemplo, se um comando ls falha porque o diretório não existe, $? será diferente de zero. Você pode testar esse valor com if ou usá-lo em expressões.

#!/bin/bash
ls /diretorio/inexistente
echo "Código de saída: $?"
# Saída: ls: cannot access '/diretorio/inexistente': No such file or directory
# Código de saída: 2

Note que $? é volátil: ele é redefinido a cada comando. Se você precisar usá-lo mais de uma vez, armazene-o em uma variável imediatamente após o comando de interesse.

exit

O comando exit encerra a execução do script e retorna um código de saída para o shell pai. Se nenhum argumento for fornecido, exit retorna o código do último comando executado. É uma boa prática usar exit com um código explícito para indicar o resultado do script.

Por exemplo, um script que valida entrada pode terminar com exit 0 para sucesso e exit 1 para erro.

#!/bin/bash
if [ -z "$1" ]; then
    echo "Erro: argumento ausente" >&2
    exit 1
fi
echo "Argumento: $1"
exit 0

O código de saída do script pode ser usado por outros scripts ou pelo usuário para saber se tudo ocorreu como esperado.

Convenções (0 = sucesso)

Por convenção, o código de saída 0 indica sucesso, enquanto qualquer valor diferente de zero indica algum tipo de erro. Isso é seguido por praticamente todos os comandos Unix e ferramentas. Os códigos de erro podem ser específicos para cada comando; por exemplo, o comando ls retorna 1 para erros menores (como permissão negada) e 2 para erros graves (como argumento inválido).

Em scripts, você deve seguir essa convenção: retorne 0 quando o script concluir com sucesso, e um código não-zero para falhas. Não há um padrão rígido para o significado de cada número, mas você pode definir seus próprios códigos (por exemplo, 1 para erro genérico, 2 para argumento inválido, 3 para arquivo não encontrado).

#!/bin/bash
# Exemplo de convenção
if [ ! -f "$1" ]; then
    echo "Arquivo não encontrado" >&2
    exit 3
fi
cat "$1"
exit 0

Encadeando por status

O Bash oferece operadores lógicos que permitem encadear comandos baseados no código de saída do comando anterior. Os mais comuns são && (AND) e || (OR).

  • comando1 && comando2: executa comando2 somente se comando1 for bem-sucedido (código 0).
  • comando1 || comando2: executa comando2 somente se comando1 falhar (código não-zero).

Esses operadores são úteis para criar sequências condicionais simples sem usar if. Por exemplo:

#!/bin/bash
mkdir /tmp/diretorio && echo "Diretório criado com sucesso"
# Se mkdir falhar (diretório já existe ou permissão negada), o echo não será executado.

ping -c 1 google.com || echo "Falha ao pingar google.com"
# Se ping for bem-sucedido, o echo é ignorado.

Você também pode combinar ambos para criar cadeias mais complexas, mas cuidado com a legibilidade. Para lógica mais elaborada, prefira estruturas if.

Boas Práticas

Sempre verifique o código de saída de comandos críticos, especialmente aqueles que podem falhar (criação de diretórios, cópia de arquivos, etc.). Documente os códigos de saída que seu script utiliza. Use set -e para interromper o script automaticamente se qualquer comando falhar, mas esteja ciente de que isso pode ser muito agressivo. Outra opção é usar trap para capturar erros. Prefira exit 1 a simplesmente deixar o script terminar naturalmente quando ocorrer um erro, para que o chamador saiba que algo deu errado.

Referências

Exercícios

  1. Escreva um script que tente criar um diretório (com mkdir) e, se falhar, exiba uma mensagem de erro e saia com código 1. Use $? para verificar o status.

    ✓ Resposta:
    #!/bin/bash
    mkdir /tmp/meudir
    if [ $? -ne 0 ]; then
        echo "Erro ao criar diretório" >&2
        exit 1
    fi
    echo "Diretório criado com sucesso"
    exit 0
    
  2. Use o operador && para executar echo "sucesso" apenas se o comando ls /etc/passwd for bem-sucedido.

    ✓ Resposta:
    ls /etc/passwd && echo "sucesso"
    
  3. Crie um script que receba um nome de arquivo como argumento. Se o arquivo existir, exiba seu conteúdo e saia com 0. Caso contrário, exiba uma mensagem de erro e saia com 2.

    ✓ Resposta:
    #!/bin/bash
    if [ -f "$1" ]; then
        cat "$1"
        exit 0
    else
        echo "Arquivo não encontrado" >&2
        exit 2
    fi
    
  4. Explique o que o seguinte comando faz: make && make install || echo "Falha na instalação"

    ✓ Resposta:

    Executa make. Se for bem-sucedido (código 0), então executa make install. Se make falhar, make install não é executado. Em ambos os casos, após a execução de make install (se ocorreu) ou diretamente após a falha de make, o comando echo "Falha na instalação" é executado somente se o comando anterior (que é make install ou make caso make install não tenha sido executado) falhar. Ou seja, a mensagem aparece se make falhar ou se make install falhar.

  5. Escreva um script que use exit com um código de saída personalizado. O script deve verificar se o número de argumentos é igual a 2; se não, saia com código 3. Caso contrário, exiba os argumentos e saia com 0.

    ✓ Resposta:
    #!/bin/bash
    if [ $# -ne 2 ]; then
        echo "Uso: $0 arg1 arg2" >&2
        exit 3
    fi
    echo "Argumento 1: $1"
    echo "Argumento 2: $2"
    exit 0