Depurando scripts
Nesta aula, você aprenderá técnicas essenciais para depurar scripts em Bash, incluindo o uso de bash -x, set -x/set +x, logging manual e a ferramenta ShellCheck. O foco é tornar o processo de encontrar e corrigir erros mais eficiente e sistemático.
Depurar scripts é uma habilidade fundamental para qualquer desenvolvedor que trabalhe com shell script. Erros de sintaxe, variáveis não definidas, expansões inesperadas e lógica incorreta são problemas comuns que podem consumir muito tempo se não forem abordados com as ferramentas certas. Nesta aula, você aprenderá as principais técnicas de depuração no Bash: desde a execução passo a passo com bash -x, passando pelo controle granular com set -x e set +x, até a implementação de logging e o uso do analisador estático ShellCheck. Ao final, você será capaz de identificar e corrigir bugs de forma mais rápida e confiável.
bash -x
O modo mais simples e direto para depurar um script Bash é executá-lo com a opção -x (ou --xtrace). Quando você roda bash -x script.sh, o shell exibe cada comando antes de executá-lo, precedido por um sinal de mais (+). Isso mostra exatamente o que está sendo executado, incluindo expansões de variáveis, substituições de comandos e redirecionamentos. É como ter um rastro de execução que revela o fluxo real do script.
Por exemplo, considere o script teste.sh:
#!/bin/bash
nome="João"
echo "Olá, $nome"
Ao executar bash -x teste.sh, a saída será algo como:
+ nome='João'
+ echo 'Olá, João'
Olá, João
Perceba que as variáveis já aparecem expandidas. Isso ajuda a identificar problemas como espaços inesperados, caracteres especiais ou variáveis não definidas. O modo -x é especialmente útil para scripts longos, pois você pode ver exatamente onde o erro ocorre. Uma dica prática: combine -x com -v (verbose) para também ver as linhas do script antes da execução (bash -xv script.sh).
set -x e set +x
Enquanto bash -x ativa o trace para todo o script, às vezes queremos depurar apenas uma parte específica. Para isso, usamos set -x para iniciar o trace e set +x para interrompê-lo. Esses comandos podem ser colocados em qualquer lugar do script, permitindo um controle fino sobre quais trechos serão rastreados.
Exemplo:
#!/bin/bash
echo "Início do script"
set -x # ativa trace a partir daqui
for i in 1 2 3; do
echo "Número: $i"
done
set +x # desativa trace
echo "Fim do script"
Ao executar, apenas o loop será exibido com +. Isso reduz o ruído e foca no trecho problemático. Você pode ativar e desativar várias vezes ao longo do script. Outra opção é usar set -o xtrace e set +o xtrace, que são equivalentes. Lembre-se de que set -x é herdado por funções e subshells, a menos que seja desativado dentro deles.
Logging
Em scripts mais complexos ou que rodam em produção, o trace pode gerar muita saída e não é prático para análise posterior. Uma alternativa é implementar logging manual, ou seja, adicionar mensagens informativas em pontos estratégicos do script, geralmente com timestamps e níveis de severidade (INFO, WARN, ERROR). Isso permite que você acompanhe o que aconteceu sem precisar executar o script interativamente.
Exemplo simples de função de log:
#!/bin/bash
LOG_FILE="script.log"
log() {
local level="$1"
shift
echo "$(date '+%Y-%m-%d %H:%M:%S') [$level] $*" | tee -a "$LOG_FILE"
}
log "INFO" "Iniciando script"
# ... comandos ...
if [ ! -d "/tmp/dados" ]; then
log "ERROR" "Diretório /tmp/dados não existe"
exit 1
fi
log "INFO" "Script concluído com sucesso"
Você pode enriquecer o log com variáveis de ambiente, códigos de retorno e até mesmo redirecionar stderr para o log. Uma boa prática é usar exec para redirecionar toda a saída do script para um arquivo de log, mas cuidado para não perder a saída interativa. O logging é essencial para scripts que rodam em cron jobs ou em segundo plano.
shellcheck (visão geral)
ShellCheck é uma ferramenta de análise estática para scripts shell. Ela não executa o script, mas examina o código fonte em busca de erros comuns, más práticas e possíveis bugs. É como um linter para Bash. ShellCheck pode ser usado online (shellcheck.net), instalado via gerenciador de pacotes (ex: sudo apt install shellcheck) ou integrado em editores como VSCode, Vim e Emacs.
Exemplo de uso:
shellcheck script.sh
ShellCheck emite avisos e erros com códigos como SC2002 (uso inútil de cat) ou SC2086 (expansão de variável sem aspas). Por exemplo, para o script:
#!/bin/bash
nome=$1
if [ $nome = "João" ]
then
echo "Olá"
fi
ShellCheck apontaria que a variável $nome deve estar entre aspas duplas para evitar splitting e globbing, e que o = dentro de [ ] deveria ser == (embora = funcione, é ambíguo). Corrigindo:
#!/bin/bash
nome="$1"
if [ "$nome" = "João" ]; then
echo "Olá"
fi
ShellCheck é uma ferramenta indispensável para escrever scripts robustos e portáveis. Incorpore-a no seu fluxo de trabalho: execute antes de testar e antes de commit. A documentação oficial explica cada aviso em detalhes.
Boas práticas e observações finais
Depurar é uma arte que combina técnica e paciência. Além das ferramentas apresentadas, lembre-se de:
- Usar
echoouprintfpara exibir valores de variáveis em pontos críticos. - Verificar códigos de retorno com
$?após comandos importantes. - Utilizar
trappara capturar sinais e depurar em caso de erro (ex:trap 'echo "Erro na linha $LINENO"' ERR). - Manter o script organizado com funções e indentação consistente.
- Testar com entradas variadas, incluindo casos extremos (espaços, caracteres especiais, variáveis vazias).
Combinando bash -x, set -x, logging e ShellCheck, você estará preparado para enfrentar qualquer bug. Pratique com scripts reais e veja como a depuração se torna mais eficiente.
Referências
- GNU Bash Manual: The Set Builtin
- GNU Bash Manual: Bash Startup Files
- ShellCheck - Online
- ShellCheck no GitHub
- GNU Bash Manual: Shell Scripts
- Bash Beginners Guide: Debugging
- BashFAQ/040: How do I debug a bash script?
Exercícios
-
Crie um script que contenha um erro proposital (ex: variável sem aspas que cause splitting). Use
bash -xpara identificar o erro e corrija-o.✓ Resposta: Exemplo de script com erro:
Ao executar#!/bin/bash # script: erro.sh dir="/tmp/pasta com espaco" ls $dir # erro: sem aspas, $dir será expandido como dois argumentosbash -x erro.sh, vemos:
Corrigido:+ dir='/tmp/pasta com espaco' + ls /tmp/pasta com espaco ls: cannot access '/tmp/pasta': No such file or directory ls: cannot access 'com': No such file or directory ls: cannot access 'espaco': No such file or directoryls "$dir". -
Escreva um script que use
set -xeset +xpara depurar apenas um loop que calcula fatorial. Mostre a saída do trace.✓ Resposta:
Saída do trace:#!/bin/bash n=5 fatorial=1 echo "Calculando fatorial de $n" set -x for (( i=1; i<=n; i++ )); do fatorial=$((fatorial * i)) done set +x echo "Resultado: $fatorial"+ (( i=1 )) + (( i<=n )) + fatorial=1 + (( i++ )) + (( i<=n )) + fatorial=2 + (( i++ )) ... -
Implemente uma função de logging que escreva em um arquivo e também exiba no terminal, com níveis INFO, WARN e ERROR. Use-a em um script simples.
✓ Resposta:#!/bin/bash LOG_FILE="meu_script.log" log() { local level="$1" shift local message="$(date '+%Y-%m-%d %H:%M:%S') [$level] $*" echo "$message" | tee -a "$LOG_FILE" } log "INFO" "Iniciando script" if [ ! -f "/etc/passwd" ]; then log "ERROR" "Arquivo /etc/passwd não encontrado" else log "INFO" "Arquivo /etc/passwd existe" fi log "INFO" "Script finalizado" -
Instale o ShellCheck (ou use a versão online) e analise o script abaixo. Liste pelo menos dois problemas apontados e corrija-os.
#!/bin/bash for i in $(ls *.txt); do echo $i done✓ Resposta: ShellCheck aponta:- SC2045: Iterating over 'ls' output is fragile. Use globs. (Melhor:
for i in *.txt) - SC2086: Double quote to prevent globbing and word splitting. (Melhor:
echo "$i")
#!/bin/bash for i in *.txt; do echo "$i" done - SC2045: Iterating over 'ls' output is fragile. Use globs. (Melhor:
-
Crie um script que usa
trappara exibir a linha do erro quando um comando falhar. Teste com um comando que retorna erro.✓ Resposta:
Ao executar, a saída será:#!/bin/bash trap 'echo "Erro na linha $LINENO"' ERR echo "Tentando acessar diretório inexistente" cd /diretorio/inexistente echo "Isso não será executado"Tentando acessar diretório inexistente Erro na linha 5