Nesta aula, você aprenderá a criar scripts de instalação e configuração (setup) robustos e profissionais em Bash. Um bom script de setup é essencial para automatizar a preparação de ambientes de desenvolvimento, servidores ou até mesmo para distribuir ferramentas. Vamos explorar quatro pilares fundamentais: verificação de dependências, idempotência, mensagens informativas e tratamento de erros. Esses conceitos garantem que seu script funcione de forma confiável, seja executado múltiplas vezes sem causar problemas e forneça feedback claro ao usuário.

Um script de instalação bem projetado não apenas executa comandos, mas também antecipa falhas, informa o progresso e pode ser reexecutado com segurança. Ao final desta aula, você será capaz de escrever scripts que lidam com ambientes reais, onde pacotes podem faltar, versões podem conflitar e erros inesperados podem ocorrer.

Verificando dependências

Antes de executar qualquer instalação, é crucial verificar se o sistema possui todas as ferramentas e pacotes necessários. Isso evita falhas no meio do processo e permite fornecer instruções claras ao usuário. Dependências podem incluir comandos (como curl, git), bibliotecas, versões específicas de software ou até mesmo permissões de superusuário.

Uma abordagem comum é usar command -v para verificar se um comando está disponível no PATH. Por exemplo, command -v git retorna o caminho do executável se existir, e um status de saída diferente de zero se não. Você pode combinar isso com verificações de versão usando --version e processamento de texto. Para pacotes que são gerenciados pelo sistema, você pode usar o gerenciador de pacotes (como dpkg, rpm, pacman) para verificar se estão instalados.

Aqui está um exemplo de função que verifica se um comando existe e, se não, tenta instalá-lo (simulação):

#!/bin/bash

check_dependency() {
    local cmd="$1"
    local pkg="$2"  # nome do pacote, se diferente

    if ! command -v "$cmd" > /dev/null 2>&1; then
        echo "Erro: $cmd não encontrado."
        if [[ -n "$pkg" ]]; then
            echo "Instalando $pkg..."
            # Comando de instalação (ajuste conforme o gerenciador)
            sudo apt-get install -y "$pkg" || {
                echo "Falha ao instalar $pkg."
                exit 1
            }
        else
            echo "Instale $cmd manualmente e tente novamente."
            exit 1
        fi
    else
        echo "OK: $cmd está disponível."
    fi
}

check_dependency "curl" "curl"
check_dependency "git" "git"
check_dependency "node" "nodejs"

Note que a função usa command -v e redireciona a saída para evitar poluir o terminal. Em sistemas baseados em Debian, o comando de instalação pode variar; em produção, você pode detectar o gerenciador de pacotes com cat /etc/os-release e agir de acordo.

Idempotência

Um script é idempotente se executá-lo várias vezes produz o mesmo resultado, sem efeitos colaterais indesejados. Isso é fundamental para scripts de setup, pois os usuários podem executá-los mais de uma vez (por exemplo, após uma falha ou para atualizar). A idempotência evita a duplicação de arquivos, a reinstalação desnecessária de pacotes e conflitos de configuração.

Para alcançar a idempotência, você pode verificar se uma ação já foi realizada antes de executá-la. Por exemplo, se você precisa criar um diretório, use mkdir -p, que não falha se o diretório já existir. Para instalar pacotes, muitos gerenciadores já são idempotentes (como apt-get install), mas você pode verificar se o pacote já está instalado para evitar mensagens redundantes. Para arquivos de configuração, você pode comparar com versões anteriores ou usar checksums.

Veja um exemplo de instalação de um pacote com verificação de presença:

#!/bin/bash

install_package() {
    local pkg="$1"
    if dpkg -s "$pkg" > /dev/null 2>&1; then
        echo "Pacote $pkg já está instalado."
    else
        echo "Instalando $pkg..."
        sudo apt-get install -y "$pkg"
    fi
}

install_package "build-essential"
install_package "python3-pip"

Outra técnica é usar um arquivo de marcação (flag) para indicar que a configuração foi concluída. Por exemplo, após criar um link simbólico, você pode verificar se ele já existe antes de criá-lo novamente. Isso garante que o script não sobrescreva configurações personalizadas do usuário sem necessidade.

Mensagens

Mensagens claras e informativas são essenciais para que o usuário entenda o que está acontecendo durante a execução do script. Uma boa prática é usar cores para destacar sucesso, erro e avisos, além de prefixos como [INFO], [ERRO] ou [OK]. Isso torna a saída mais legível e profissional.

Você pode definir funções para mensagens padronizadas, como:

#!/bin/bash

# Cores ANSI
GREEN='\033[0;32m'
RED='\033[0;31m'
YELLOW='\033[0;33m'
NC='\033[0m' # Sem cor

info() {
    echo -e "${YELLOW}[INFO]${NC} $1"
}

success() {
    echo -e "${GREEN}[OK]${NC} $1"
}

error() {
    echo -e "${RED}[ERRO]${NC} $1" >&2
}

info "Iniciando setup..."
# ... comandos ...
success "Setup concluído!"

Além de mensagens no terminal, você pode usar barras de progresso (com pv ou dialog) para operações longas, mas isso pode ser complexo. Para a maioria dos casos, mensagens simples com níveis de severidade são suficientes. Lembre-se de que mensagens de erro devem ir para stderr (usando >&2) para que possam ser separadas da saída normal.

Tratamento de erros

Em scripts de instalação, erros podem ocorrer por diversos motivos: falta de permissão, rede indisponível, pacote corrompido, etc. Um bom script deve detectar esses erros e agir de forma controlada, seja saindo com uma mensagem clara ou tentando uma recuperação. O Bash oferece algumas ferramentas para isso: set -e para sair em caso de erro, trap para capturar sinais e limpar recursos, e verificação do código de saída de cada comando.

Usar set -e é uma prática comum, mas pode ser perigoso em scripts longos, pois qualquer comando que falhe (mesmo em uma condição) encerra o script. Uma abordagem mais controlada é usar if ou || para tratar erros explicitamente. Por exemplo:

#!/bin/bash

set -e  # sair em caso de erro em qualquer comando (exceto em condições)

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

echo "Baixando arquivo..."
if ! curl -fSL "https://exemplo.com/arquivo.tar.gz" -o /tmp/arquivo.tar.gz; then
    echo "Falha no download." >&2
    exit 1
fi

echo "Extraindo..."
tar -xzf /tmp/arquivo.tar.gz -C /opt

echo "Configurando..."
# ... outros comandos ...

O trap com ERR captura qualquer erro que cause o script a sair (por causa do set -e ou de um exit explícito). Você pode usá-lo para limpar arquivos temporários ou restaurar o estado anterior. Em scripts mais complexos, é comum implementar funções de rollback, que desfazem as ações já executadas em caso de falha.

Outro aspecto é verificar se o script está sendo executado como root quando necessário, usando id -u:

if [[ $EUID -ne 0 ]]; then
    echo "Este script precisa ser executado como root." >&2
    exit 1
fi

Boas práticas e observações finais

Ao escrever scripts de setup, mantenha o código organizado em funções, use nomes descritivos e comente as partes não óbvias. Teste o script em um ambiente controlado antes de distribuí-lo. Considere também a portabilidade entre distribuições Linux, detectando o gerenciador de pacotes. Por fim, documente o script com uma mensagem de ajuda (-h) e forneça opções para personalização, como instalar apenas determinadas partes.

Exercícios

  1. Escreva uma função check_command que recebe o nome de um comando e, se não estiver disponível, exibe uma mensagem de erro e sai do script com código 1. Teste com git e curl.
  2. ✓ Resposta:
    #!/bin/bash
    
    check_command() {
        local cmd="$1"
        if ! command -v "$cmd" > /dev/null 2>&1; then
            echo "Erro: $cmd não está instalado." >&2
            exit 1
        fi
    }
    
    check_command git
    check_command curl
    echo "Tudo ok!"
    
  3. Crie um script que instale o pacote htop de forma idempotente, verificando se ele já está instalado antes de chamar o gerenciador de pacotes. Use dpkg -s para verificação.
  4. ✓ Resposta:
    #!/bin/bash
    
    if dpkg -s htop > /dev/null 2>&1; then
        echo "htop já está instalado."
    else
        echo "Instalando htop..."
        sudo apt-get install -y htop
    fi
    
  5. Adicione funções de mensagem info, success e error com cores ANSI e use-as em um script que simula o download e a instalação de um pacote fictício.
  6. ✓ Resposta:
    #!/bin/bash
    
    GREEN='\033[0;32m'
    RED='\033[0;31m'
    YELLOW='\033[0;33m'
    NC='\033[0m'
    
    info() { echo -e "${YELLOW}[INFO]${NC} $1"; }
    success() { echo -e "${GREEN}[OK]${NC} $1"; }
    error() { echo -e "${RED}[ERRO]${NC} $1" >&2; }
    
    info "Baixando pacote..."
    # Simula download
    sleep 1
    success "Download concluído."
    info "Instalando..."
    # Simula instalação
    sleep 1
    if true; then
        success "Instalação concluída."
    else
        error "Falha na instalação."
        exit 1
    fi
    
  7. Escreva um script que use set -e e um trap ERR para capturar erros e exibir a linha onde ocorreu. O script deve tentar criar um diretório e falhar propositalmente (por exemplo, usando um caminho inválido).
  8. ✓ Resposta:
    #!/bin/bash
    
    set -e
    trap 'echo "Erro na linha $LINENO" >&2' ERR
    
    echo "Criando diretório..."
    mkdir /root/teste  # Isso falhará se não for root
    
    echo "Diretório criado."
    
  9. Implemente uma função install_or_skip que recebe um comando e um pacote. Se o comando não existir, tenta instalar o pacote; se a instalação falhar, sai com código 1. Caso contrário, exibe uma mensagem de sucesso.
  10. ✓ Resposta:
    #!/bin/bash
    
    install_or_skip() {
        local cmd="$1"
        local pkg="$2"
        if command -v "$cmd" > /dev/null 2>&1; then
            echo "$cmd já está disponível."
        else
            echo "Instalando $pkg..."
            if ! sudo apt-get install -y "$pkg"; then
                echo "Falha ao instalar $pkg." >&2
                exit 1
            fi
            echo "$cmd instalado com sucesso."
        fi
    }
    
    install_or_skip "htop" "htop"
    install_or_skip "jq" "jq"
    

Referências