Debugging de scripts com set -x

1. Introdução ao set -x: O que é e para que serve

1.1. Definição: ativa o modo de rastreamento (trace mode) no Bash

O comando set -x ativa o modo de rastreamento (trace mode) no Bash. Quando ativado, o shell exibe cada comando antes de executá-lo, permitindo que o desenvolvedor acompanhe passo a passo a execução do script. Essa é uma das ferramentas mais poderosas para depuração de scripts shell.

1.2. Comportamento básico: exibe cada comando antes de executá-lo, precedido por +

Quando o modo trace está ativo, cada comando é impresso no stderr precedido pelo caractere +. Isso inclui comandos simples, estruturas de controle, expansões de variáveis e substituições de comandos.

#!/bin/bash
set -x
nome="João"
echo "Olá, $nome"
set +x

Saída esperada:

+ nome=João
+ echo 'Olá, João'
Olá, João

1.3. Diferença entre set -x e outras opções de depuração (set -v, set -n)

O Bash oferece três opções principais de depuração:

  • set -x: Exibe comandos após expansão (mostra valores reais das variáveis)
  • set -v: Exibe comandos antes da expansão (mostra o código fonte literal)
  • set -n: Apenas interpreta o script sem executar (verifica erros de sintaxe)
#!/bin/bash
# Exemplo comparativo
var="mundo"
echo "Olá, $var"

Com set -v:

echo "Olá, $var"
Olá, mundo

Com set -x:

+ echo 'Olá, mundo'
Olá, mundo

2. Ativando e desativando o modo de depuração

2.1. Uso global: set -x no início do script e set +x para desativar

Para depurar um script inteiro, coloque set -x logo após o shebang:

#!/bin/bash
set -x

# Todo o script será rastreado
contador=0
while [ $contador -lt 3 ]; do
    echo "Iteração $contador"
    ((contador++))
done
set +x

2.2. Uso localizado: blocos específicos com set -x e set +x

Para depurar apenas seções problemáticas:

#!/bin/bash
echo "Início do script (sem debug)"

set -x  # Ativa debug
for arquivo in *.txt; do
    wc -l "$arquivo"
done
set +x  # Desativa debug

echo "Fim do script (sem debug)"

2.3. Execução direta na linha de comando: bash -x script.sh

Sem modificar o script, execute-o com a flag -x:

bash -x meu_script.sh

Isso ativa o trace mode para toda a execução, independentemente de set -x estar presente no código.

3. Interpretando a saída do set -x

3.1. O significado do prefixo + e níveis de aninhamento (expansão de ++)

Cada nível de aninhamento adiciona um + extra. Subshells, substituições de comandos e pipes dentro de estruturas complexas geram níveis mais profundos:

#!/bin/bash
set -x
resultado=$(echo "subshell" | tr 'a-z' 'A-Z')
echo "$resultado"

Saída:

++ echo subshell
++ tr a-z A-Z
+ resultado=SUBSHELL
+ echo SUBSHELL
SUBSHELL

3.2. Visualização de variáveis expandidas: valores reais vs. nomes das variáveis

O set -x mostra o comando com as variáveis já expandidas, facilitando a identificação de valores inesperados:

#!/bin/bash
set -x
arquivo="/tmp/meu arquivo.txt"
cat $arquivo      # Problema: sem aspas
cat "$arquivo"    # Correto: com aspas

Saída:

+ arquivo='/tmp/meu arquivo.txt'
+ cat /tmp/meu arquivo.txt   # Mostra dois argumentos separados
cat: /tmp/meu: No such file or directory
cat: arquivo.txt: No such file or directory
+ cat '/tmp/meu arquivo.txt' # Mostra um único argumento

3.3. Identificação de comandos, pipes, redirecionamentos e subshells na saída

#!/bin/bash
set -x
ls -la | grep ".txt" > arquivos.txt
(echo "subshell"; pwd)

Saída:

+ ls -la
+ grep .txt
+ ls -la
+ grep .txt
++ echo subshell
++ pwd
+ echo subshell
+ pwd
subshell
/home/usuario

4. Casos práticos de uso do set -x

4.1. Depuração de loops: verificando iterações e valores de variáveis

#!/bin/bash
set -x
soma=0
for i in {1..5}; do
    ((soma += i))
    echo "Soma parcial: $soma"
done
echo "Soma total: $soma"

4.2. Depuração de condicionais (if, case): vendo o fluxo de decisão

#!/bin/bash
set -x
arquivo="/etc/passwd"

if [ -f "$arquivo" ] && [ -r "$arquivo" ]; then
    echo "Arquivo existe e é legível"
elif [ -f "$arquivo" ]; then
    echo "Arquivo existe mas não é legível"
else
    echo "Arquivo não existe"
fi

4.3. Depuração de funções: rastreamento de chamadas e parâmetros

#!/bin/bash
set -x

calcular_media() {
    local total=$(($1 + $2 + $3))
    local media=$(echo "scale=2; $total / 3" | bc)
    echo "Média: $media"
}

calcular_media 8 7 9

5. Combinando set -x com outras ferramentas de depuração

5.1. Uso conjunto com set -e (exit on error) e set -u (unset variables)

#!/bin/bash
set -eux  # Combina exit on error, unset variables e trace mode

# O script para ao primeiro erro e mostra cada comando
nome="Maria"
echo "$nome"
echo "$sobrenome"  # Isso causará erro (variável não definida)

5.2. Redirecionamento da saída de depuração para um arquivo: exec 2>debug.log

Para não poluir a saída padrão:

#!/bin/bash
exec 2>debug.log  # Redireciona stderr (onde o set -x escreve)
set -x

echo "Processando..."
# ... resto do script

5.3. Integração com PS4 para personalizar o prompt de depuração

A variável PS4 controla o prefixo exibido pelo set -x:

#!/bin/bash
export PS4='+ [LINHA $LINENO] '  # Mostra o número da linha

set -x
nome="Ana"
echo "Olá, $nome"

Saída:

+ [LINHA 5] nome=Ana
+ [LINHA 6] echo 'Olá, Ana'
Olá, Ana

6. Boas práticas e armadilhas comuns

6.1. Cuidado com informações sensíveis: senhas e tokens em variáveis

#!/bin/bash
set -x
senha="minha_senha_secreta_123"  # ISSO SERÁ EXIBIDO NO LOG!
echo "Autenticando..."

Solução: Desative set -x antes de manipular dados sensíveis.

6.2. Impacto na performance: evitar set -x em scripts de produção

O set -x adiciona overhead significativo. Use apenas durante desenvolvimento e depuração.

6.3. Desativação segura: uso de trap para desligar set -x em caso de erro

#!/bin/bash
set -x
trap 'set +x; echo "Debug desativado após erro"' ERR

# Código que pode falhar
comando_inexistente
echo "Isso não será executado"

7. Alternativas e complementos ao set -x

7.1. Uso de bash -x vs. set -x dentro do script

  • bash -x script.sh: Ideal para depuração única, sem modificar o script
  • set -x no script: Útil para depuração contínua ou seletiva

7.2. Depuração com trap 'echo "Erro na linha $LINENO"' ERR para pontos específicos

#!/bin/bash
trap 'echo "ERRO na linha $LINENO: comando falhou"' ERR

comando1
comando_inexistente  # Isso dispara o trap
comando2

7.3. Ferramentas externas: shellcheck para análise estática combinada com set -x

# Instalação (Linux)
sudo apt install shellcheck

# Uso
shellcheck meu_script.sh

O ShellCheck identifica problemas comuns que o set -x ajuda a confirmar durante a execução.

8. Exemplo completo: depurando um script real

8.1. Script com bug (loop infinito)

#!/bin/bash
# Script para processar arquivos de log
contador=0
total_linhas=0

while [ $contador -lt 5 ]; do
    for arquivo in *.log; do
        linhas=$(wc -l < "$arquivo")
        total_linhas=$((total_linhas + linhas))
        echo "Processado: $arquivo ($linhas linhas)"
    done
    # BUG: contador nunca é incrementado!
done

echo "Total de linhas: $total_linhas"

8.2. Aplicação passo a passo do set -x para identificar o problema

#!/bin/bash
set -x  # Ativa debug
contador=0
total_linhas=0

while [ $contador -lt 5 ]; do
    for arquivo in *.log; do
        linhas=$(wc -l < "$arquivo")
        total_linhas=$((total_linhas + linhas))
        echo "Processado: $arquivo ($linhas linhas)"
    done
    # Observamos que contador nunca muda
done

set +x
echo "Total de linhas: $total_linhas"

Ao executar com set -x, vemos claramente que o valor de contador permanece 0 em todas as iterações.

8.3. Correção do script e verificação com set -x desativado

#!/bin/bash
contador=0
total_linhas=0

while [ $contador -lt 5 ]; do
    for arquivo in *.log; do
        linhas=$(wc -l < "$arquivo")
        total_linhas=$((total_linhas + linhas))
        echo "Processado: $arquivo ($linhas linhas)"
    done
    ((contador++))  # CORREÇÃO: incrementa o contador
done

echo "Total de linhas: $total_linhas"

Após a correção, podemos remover o set -x ou mantê-lo apenas para verificar se a solução funciona corretamente.

Referências