Chegamos à reta final do nosso curso! Ao longo de 97 aulas, você aprendeu a criar scripts poderosos, automatizar tarefas, manipular textos, lidar com erros e muito mais. Mas um script que funciona é apenas o começo. Nesta aula de revisão, vamos consolidar as boas práticas que separam um código amador de um profissional. Vamos pensar além da funcionalidade: qualidade, manutenção, documentação e versionamento são os pilares para que seus scripts sejam confiáveis, legíveis e evoluam sem dor de cabeça.

Este é o momento de internalizar hábitos que vão te acompanhar em qualquer projeto, seja um script de 10 linhas ou um sistema complexo. Vamos mergulhar em cada um desses pilares, com exemplos práticos e referências para aprofundamento.

Checklist de qualidade

Antes de considerar um script 'pronto', é essencial passar por um checklist que garanta sua robustez e aderência a padrões. Isso evita surpresas em produção e facilita a vida de quem for usar (incluindo o seu 'eu' do futuro). Abaixo, uma lista de verificação que você pode aplicar a qualquer script.

  • Shebang correto: #!/usr/bin/env bash para portabilidade ou caminho absoluto.
  • Opções do shell: set -euo pipefail para sair em erros, tratar variáveis não definidas e falhas em pipelines.
  • Variáveis globais em MAIÚSCULAS: facilita a identificação.
  • Funções nomeadas com verbos: processar_arquivo(), validar_entrada().
  • Tratamento de argumentos: usar getopts ou validação manual com mensagens de uso.
  • Checagem de dependências: verificar se comandos externos existem.
  • Mensagens de erro claras: incluir contexto e sair com código de status apropriado.
  • Sem execução desnecessária com privilégios: evitar sudo dentro do script, a menos que necessário.
  • Código limpo e indentado: consistência no uso de espaços ou tabs.
  • Tratamento de sinais: usar trap para limpar recursos temporários.

Exemplo de estrutura inicial de um script seguindo o checklist:

#!/usr/bin/env bash
set -euo pipefail

# Constantes
readonly SCRIPT_NOME="$(basename "$0")"
readonly DIR_TEMP="$(mktemp -d)"

# Trap para limpeza
trap 'rm -rf "$DIR_TEMP"' EXIT

# Funções
ajuda() {
    cat <<EOF
Uso: $SCRIPT_NOME [opções]
Opções:
  -h, --help  Mostra esta ajuda
EOF
}

# Verificação de dependências
for cmd in curl jq; do
    if ! command -v "$cmd" >/dev/null 2>&1; then
        echo "Erro: comando '$cmd' não encontrado. Instale-o para continuar." >&2
        exit 1
    fi
done

# Processamento de argumentos
while getopts ":h" opt; do
    case "$opt" in
        h)
            ajuda
            exit 0
            ;;
        \?)
            echo "Opção inválida: -$OPTARG" >&2
            ajuda
            exit 1
            ;;
    esac
done

# Corpo principal
...
EOF

Aplicar esse checklist sistematicamente reduz bugs e melhora a experiência de quem mantém o código. Lembre-se: qualidade não é acidental, é uma decisão consciente.

Manutenibilidade

Um script manutenível é aquele que pode ser entendido, modificado e estendido com facilidade. Isso exige organização, simplicidade e consistência. Scripts longos e monolíticos são difíceis de depurar e arriscados de alterar. A chave é dividir o problema em partes menores.

Princípios de manutenibilidade:

  • Modularização: quebre o script em funções pequenas e coesas. Cada função deve fazer apenas uma coisa.
  • Nomes descritivos: evite x, y; use arquivo_entrada, usuario.
  • Comentários estratégicos: explique o 'porquê', não o 'o quê'. O código já mostra o que faz.
  • Evite duplicação: se você copiou e colou, extraia para uma função.
  • Use constantes para valores mágicos: em vez de if [[ $? -eq 0 ]], use readonly SUCESSO=0.

Exemplo de refatoração de um trecho duplicado:

# Antes: código duplicado para validar arquivos
if [[ -f "$1" ]]; then
    echo "Processando $1"
fi
if [[ -f "$2" ]]; then
    echo "Processando $2"
fi

# Depois: função reutilizável
processar_arquivo() {
    local arquivo="$1"
    if [[ -f "$arquivo" ]]; then
        echo "Processando $arquivo"
    else
        echo "Aviso: arquivo $arquivo não encontrado" >&2
    fi
}
processar_arquivo "$1"
processar_arquivo "$2"

Outra prática recomendada é usar o shellcheck para análise estática. Ele aponta erros comuns e sugere melhorias. Integrá-lo ao seu fluxo de trabalho é um passo enorme para a qualidade.

# Instalar (exemplo no Ubuntu)
sudo apt install shellcheck

# Usar
shellcheck script.sh

Manutenibilidade não é um luxo, é uma necessidade para projetos que crescem. Invista tempo em escrever código claro e você colherá os frutos no futuro.

Documentação

Documentação é a ponte entre o código e os seres humanos. Ela pode ser interna (comentários) e externa (README, man pages). Um script bem documentado é mais confiável e mais fácil de adotar. Mas documentar bem não é despejar texto; é fornecer informações relevantes de forma organizada.

Documentação interna:

  • Cabeçalho do script: incluir nome, descrição, autor, data, versão e como usar.
  • Comentários em trechos complexos: explique a lógica, especialmente se não for óbvia.
  • Documentação de funções: descrever parâmetros, retorno e efeitos colaterais.
#!/usr/bin/env bash
#===============================================================================
# backup.sh - Cria backup compactado de um diretório
#
# Uso: ./backup.sh <diretorio_origem> <arquivo_destino>
#
# Exemplo:
#   ./backup.sh /home/user/documentos /backup/documentos.tar.gz
#
# Autor: João Silva
# Data: 2025-01-01
# Versão: 1.0
#===============================================================================

# Função para validar argumentos
# Uso: validar_args "$@"
# Retorna 0 se ok, 1 se erro
validar_args() {
    if [[ $# -ne 2 ]]; then
        echo "Erro: número de argumentos inválido." >&2
        exit 1
    fi
}

Documentação externa:

  • README.md: com visão geral, instalação, uso, exemplos e link para mais informações.
  • Man pages: para scripts distribuídos, criar uma página de manual usando pandoc ou ronn.
  • Wiki ou docs online: para projetos maiores.

Além disso, use a opção --help em seus scripts, fornecendo uma mensagem de ajuda clara. Isso é documentação viva, acessível ao usuário.

# Trecho de exemplo
ajuda() {
    cat <<EOF
Uso: $(basename "$0") [OPÇÕES]

Opções:
  -h, --help     Exibe esta ajuda
  -v, --verbose  Modo verboso
EOF
}

# No processamento de argumentos
-h|--help)
    ajuda
    exit 0
    ;;

Documentar é um ato de respeito com quem vai usar e manter o código. Não pule essa etapa.

Versionamento

Versionar seus scripts com Git é essencial para acompanhar mudanças, colaborar e reverter para versões anteriores quando necessário. Mesmo scripts pessoais se beneficiam do controle de versão. Nesta seção, vamos revisar as práticas recomendadas para versionar scripts shell.

Configuração inicial:

  • Inicie um repositório: git init no diretório do projeto.
  • Crie um .gitignore: para excluir arquivos temporários e sensíveis.
  • Faça commits pequenos e frequentes: com mensagens descritivas.

Exemplo de .gitignore para scripts:

# Arquivos temporários
*.tmp
*.log

# Diretórios de backup
backups/

# Arquivos de ambiente
.env

Boas práticas de commit:

  • Mensagens claras: use o imperativo: "Adiciona função de validação".
  • Separe mudanças lógicas: um commit por tarefa.
  • Use branches: para experimentos ou novas funcionalidades.
  • Tags para versões: git tag v1.0.0 para releases.

Além disso, considere usar hooks do Git, como um hook de pre-commit que roda o shellcheck automaticamente. Isso garante que o código commitado siga padrões de qualidade.

# .git/hooks/pre-commit (exemplo)
#!/usr/bin/env bash
# Executa shellcheck em todos os scripts .sh modificados
for file in $(git diff --cached --name-only -- '*.sh'); do
    if ! shellcheck "$file"; then
        echo "Erro: shellcheck falhou em $file" >&2
        exit 1
    fi
done
exit 0

O versionamento também facilita a revisão de código e a colaboração em equipe. Mesmo que você trabalhe sozinho, o Git é um seguro contra desastres.

Boas práticas finais

Além dos pilares acima, aqui vão algumas observações finais para elevar ainda mais a qualidade dos seus scripts:

  • Teste seus scripts: escreva testes simples ou use ferramentas como shunit2.
  • Considere o uso de frameworks: como bashunit para estruturação.
  • Mantenha a compatibilidade: teste com diferentes versões do Bash e em outras plataformas (se possível).
  • Pense em segurança: evite injeção de comandos usando arrays e aspas corretamente.
  • Compartilhe e aprenda: publique seus scripts em plataformas como GitHub para receber feedback.

Lembre-se: a prática leva à perfeição. Continue escrevendo, refatorando e melhorando seus scripts. As boas práticas se tornam hábitos com o tempo, e você verá seus scripts se tornarem mais confiáveis e fáceis de manter.

Referências

Exercícios

  1. Refatore o seguinte script para seguir as boas práticas de qualidade (use set -euo pipefail, funções, validação de argumentos, etc.):

    #!/bin/bash
    # Script antigo
    cp $1 $2
    echo "Arquivo copiado"

    ✓ Resposta:
    #!/usr/bin/env bash
    set -euo pipefail
    
    # Função de uso
    uso() {
        cat <<EOF
    Uso: $(basename "$0") <origem> <destino>
    EOF
        exit 1
    }
    
    # Validação de argumentos
    [[ $# -eq 2 ]] || uso
    
    origem="$1"
    destino="$2"
    
    # Verifica se origem existe
    if [[ ! -f "$origem" ]]; then
        echo "Erro: arquivo origem '$origem' não encontrado." >&2
        exit 1
    fi
    
    cp "$origem" "$destino"
    echo "Arquivo copiado com sucesso."
  2. Adicione documentação interna (cabeçalho e comentários) ao script abaixo, explicando o que ele faz e como usar:

    #!/usr/bin/env bash
    # Script sem documentação
    find /var/log -name "*.log" -mtime +7 -exec rm {} \;
    echo "Logs antigos removidos."

    ✓ Resposta:
    #!/usr/bin/env bash
    #===============================================================================
    # limpar_logs.sh - Remove arquivos .log antigos (mais de 7 dias) do diretório /var/log
    #
    # Uso: ./limpar_logs.sh
    #
    # Este script percorre /var/log, encontra arquivos .log com mais de 7 dias e os exclui.
    # Cuidado: a exclusão é permanente. Use com responsabilidade.
    #
    # Autor: Seu Nome
    # Data: 2025-01-01
    # Versão: 1.0
    #===============================================================================
    
    set -euo pipefail
    
    # Diretório de logs (pode ser alterado)
    LOG_DIR="/var/log"
    
    # Remove arquivos .log modificados há mais de 7 dias
    find "$LOG_DIR" -name "*.log" -mtime +7 -exec rm {} \;
    
    echo "Logs antigos removidos com sucesso."
  3. Escreva um script que use um trap para limpar um arquivo temporário ao sair (considere os sinais EXIT e INT).

    ✓ Resposta:
    #!/usr/bin/env bash
    set -euo pipefail
    
    # Cria arquivo temporário
    TEMP_FILE="$(mktemp)"
    
    # Função de limpeza
    limpar() {
        rm -f "$TEMP_FILE"
        echo "Arquivo temporário removido."
    }
    
    # Registra a limpeza para os sinais EXIT e INT
    trap limpar EXIT INT
    
    # Simula algum trabalho
    echo "Trabalhando..."
    sleep 5
    
    # O trap será chamado ao final ou se o script for interrompido
  4. Explique a importância de usar set -euo pipefail e dê um exemplo de como ele evita erros silenciosos.

    ✓ Resposta:

    set -e faz o script sair imediatamente se qualquer comando retornar um status diferente de zero. -u trata o uso de variáveis não definidas como erro. -o pipefail faz com que o pipeline retorne o status do último comando que falhou, em vez do último comando. Isso evita que erros sejam ignorados silenciosamente, como no exemplo:

    #!/usr/bin/env bash
    # Sem pipefail
    false | echo "Isso ainda executa"
    echo "O script continua mesmo com erro."
    
    # Com pipefail
    set -o pipefail
    false | echo "Isso executa, mas o pipeline falha"
    echo "Este comando não será alcançado."
  5. Crie um script que use getopts para aceitar as opções -v (verbose) e -h (help), e que imprima uma mensagem diferente se -v for passado.

    ✓ Resposta:
    #!/usr/bin/env bash
    set -euo pipefail
    
    VERBOSE=false
    
    uso() {
        cat <<EOF
    Uso: $(basename "$0") [-v] [-h]
    
    Opções:
      -v  Modo verboso
      -h  Exibe esta ajuda
    EOF
        exit 0
    }
    
    while getopts ":vh" opt; do
        case "$opt" in
            v)
                VERBOSE=true
                ;;
            h)
                uso
                ;;
            \?)
                echo "Opção inválida: -$OPTARG" >&2
                uso
                ;;
        esac
    done
    
    if [[ "$VERBOSE" == true ]]; then
        echo "Modo verboso ativado."
    else
        echo "Execução normal."
    fi