Em shell script, erros silenciosos podem passar despercebidos e causar comportamentos inesperados ou falhas em produção. O comando set permite modificar o comportamento do shell, habilitando opções que tornam os scripts mais previsíveis e seguros. Nesta aula, exploraremos quatro opções essenciais: -e, -u, -o pipefail e -x, que são fundamentais para escrever scripts robustos e depuráveis.

Essas opções são frequentemente usadas no início de scripts (após o shebang) para definir um modo rigoroso. Combiná-las adequadamente ajuda a evitar que o script continue executando após erros, acesse variáveis não definidas ou ignore falhas em pipelines, além de fornecer rastreamento detalhado para depuração.

set -e

A opção set -e (ou set -o errexit) faz com que o shell saia imediatamente se um comando retornar um status de saída diferente de zero (indicando erro). Isso evita que o script prossiga após uma falha, o que poderia causar efeitos colaterais indesejados ou mascarar problemas.

Por exemplo, sem set -e, um comando que falha não interrompe o script, permitindo que comandos subsequentes sejam executados. Com set -e, qualquer erro (exceto em condições controladas, como em testes if ou em comandos em &&/||) encerra o script imediatamente. Isso é especialmente útil em scripts de automação e implantação.

#!/bin/bash
set -e

echo "Antes do erro"
false  # este comando falha (status 1)
echo "Depois do erro"  # esta linha nunca será executada

No exemplo acima, a mensagem "Depois do erro" não aparece porque false retorna 1 e o script termina. É importante notar que comandos em condições (como if false; then ...) são tratados de forma especial e não disparam set -e, pois o shell espera que eles possam falhar.

set -u

A opção set -u (ou set -o nounset) trata variáveis não definidas como erro. Qualquer tentativa de expandir uma variável que não foi definida resulta em uma mensagem de erro e a saída do script (a menos que combinada com set +e). Isso evita problemas causados por digitação incorreta de nomes de variáveis ou uso de variáveis que não foram inicializadas.

Por padrão, o bash expande variáveis não definidas para uma string vazia, o que pode levar a comportamentos silenciosos e difíceis de depurar. Com set -u, o script falha imediatamente, alertando o desenvolvedor sobre o uso de variáveis inexistentes.

#!/bin/bash
set -u

echo "Nome: $nome"  # se $nome não foi definida, o script para com erro
echo "Isso não será executado"

Para evitar erros com variáveis que podem ou não estar definidas, você pode usar expansão condicional: echo "${nome:-}" ou ${nome:?Mensagem de erro}. A combinação set -u com set -e é comum para scripts críticos.

set -o pipefail

Em pipelines, o status de saída é o do último comando, mesmo que comandos anteriores tenham falhado. A opção set -o pipefail altera esse comportamento: o pipeline falha se qualquer comando no pipeline falhar, retornando o status do último comando que falhou (ou zero se todos tiverem sucesso).

Isso é crucial quando você processa dados através de múltiplos comandos encadeados. Sem pipefail, um erro em um estágio intermediário pode ser ignorado, e o script continua como se tudo estivesse correto.

#!/bin/bash
set -e
set -o pipefail

# Exemplo: listar arquivos, filtrar e contar
ls /etc/passwd 2>/dev/null | grep "root" | wc -l
# Se 'ls' falhar (arquivo não existe), o pipeline falha e o script para

No exemplo, se ls /etc/passwd falhar (o arquivo realmente existe, mas suponha que não), o pipeline inteiro falha. Sem pipefail, o comando wc -l receberia entrada vazia e retornaria 0, e o script continuaria ignorando o erro.

set -x (debug)

A opção set -x (ou set -o xtrace) ativa o modo de depuração, que imprime cada comando antes de executá-lo, expandindo variáveis e mostrando o resultado. É extremamente útil para entender o fluxo do script e identificar onde ocorrem erros.

Quando ativado, o shell exibe cada comando em uma linha precedida por um sinal de mais (+), seguido do valor expandido. Você pode ativar e desativar seletivamente com set -x e set +x para depurar apenas partes específicas do script.

#!/bin/bash
set -x  # ativa debug a partir daqui

nome="Mundo"
echo "Olá, $nome"

set +x  # desativa debug
echo "Isso não será tracejado"

A saída será algo como:

+ nome=Mundo
+ echo 'Olá, Mundo'
Olá, Mundo
+ set +x
Isso não será tracejado

Para scripts maiores, é recomendável usar set -x apenas em blocos específicos, pois a saída pode se tornar muito verbosa. Você também pode usar bash -x script.sh para depurar o script inteiro sem modificar o código.

Boas práticas

Combine as opções no início do script para obter um modo rigoroso: set -euo pipefail. Essa combinação é amplamente adotada na comunidade para scripts de produção. Lembre-se de que set -u pode causar problemas com variáveis de ambiente opcionais; use expansão condicional para tratá-las. Para depuração, ative set -x temporariamente ou execute o script com bash -x. Sempre documente o uso dessas opções para que outros desenvolvedores entendam o comportamento esperado.

Referências

Exercícios

  1. Escreva um script que use set -e e set -u. Defina uma variável arquivo e tente exibi-la. Em seguida, tente exibir uma variável inexistente. O que acontece?

    ✓ Resposta:

    O script falha ao tentar exibir $inexistente porque set -u trata variáveis não definidas como erro. O script para e exibe uma mensagem de erro como: script.sh: line X: inexistente: unbound variable. A linha echo $arquivo é executada normalmente se a variável estiver definida.

    #!/bin/bash
    set -e
    set -u
    
    arquivo="dados.txt"
    echo "Arquivo: $arquivo"
    echo "Inexistente: $inexistente"  # erro aqui
    echo "Fim"  # não executa
  2. Crie um script que demonstre o uso de set -o pipefail. Use um pipeline que falhe em um comando intermediário (por exemplo, ls /inexistente | wc -l) e mostre que o script para com set -e.

    ✓ Resposta:

    Com set -o pipefail e set -e, o pipeline falha e o script termina. O comando ls /inexistente gera erro, e o status do pipeline é não-zero, interrompendo o script.

    #!/bin/bash
    set -e
    set -o pipefail
    
    ls /inexistente | wc -l
    echo "Isso não será impresso"
  3. Use set -x para depurar um script que calcula a soma de dois números passados como argumentos. Mostre a saída tracejada.

    ✓ Resposta:

    O script com set -x imprime cada comando antes de executar. Exemplo:

    #!/bin/bash
    set -x
    
    num1=$1
    num2=$2
    soma=$((num1 + num2))
    echo "A soma é $soma"

    Saída (supondo argumentos 3 e 5):

    + num1=3
    + num2=5
    + soma=8
    + echo 'A soma é 8'
    A soma é 8
  4. Explique a diferença entre set -e e set -u. Dê um exemplo onde set -e não pegaria um erro, mas set -u sim.

    ✓ Resposta:

    set -e interrompe o script quando um comando retorna status não-zero. set -u interrompe quando uma variável não definida é expandida. Um erro que set -e não pegaria é o uso de uma variável não definida que resulta em string vazia (sem falha de comando). Por exemplo:

    #!/bin/bash
    set -e
    nome=
    echo "Olá, $nome"  # $nome não definida, mas expande para vazio, sem erro
    echo "Continuando..."

    Com set -u, a expansão de $nome causaria erro. Portanto, set -u detecta variáveis não definidas que set -e ignora.

  5. Crie um script que combine set -euo pipefail e realize as seguintes tarefas: (a) crie um diretório temporário, (b) copie um arquivo para ele, (c) liste o conteúdo. Se qualquer etapa falhar, o script deve parar. Mostre o código.

    ✓ Resposta:
    #!/bin/bash
    set -euo pipefail
    
    TMPDIR=$(mktemp -d)
    echo "Diretório criado: $TMPDIR"
    
    cp /etc/passwd "$TMPDIR"
    ls -l "$TMPDIR"
    
    rm -rf "$TMPDIR"
    echo "Script concluído com sucesso."

    Se qualquer comando falhar (por exemplo, cp sem permissão), o script para imediatamente. O uso de set -u garante que $TMPDIR esteja definida. pipefail não é usado aqui, mas está incluído para consistência.