Script de instalação/setup
LidoAula sobre como criar scripts de instalação/setup eficientes em Bash, cobrindo verificação de dependências, idempotência, mensagens claras e tratamento de erros, com exemplos práticos e exercícios.
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
- Escreva uma função
check_commandque 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 comgitecurl. - Crie um script que instale o pacote
htopde forma idempotente, verificando se ele já está instalado antes de chamar o gerenciador de pacotes. Usedpkg -spara verificação. - Adicione funções de mensagem
info,successeerrorcom cores ANSI e use-as em um script que simula o download e a instalação de um pacote fictício. - Escreva um script que use
set -ee umtrap ERRpara 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). - Implemente uma função
install_or_skipque 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.
#!/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!"
#!/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
#!/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
#!/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."
#!/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"