Escrever scripts Bash robustos é essencial para automações confiáveis. O comando set do Bash permite modificar o comportamento do shell em tempo de execução, ativando opções que previnem falhas silenciosas e facilitam a depuração. Nesta aula, exploraremos as quatro opções mais importantes: set -e, set -u, set -o pipefail e set -x. Ao final, você será capaz de escrever scripts que param ao encontrar erros, alertam sobre variáveis não definidas, tratam corretamente falhas em pipes e exibem cada comando executado.

set -e

A opção set -e (ou set -o errexit) faz com que o script termine imediatamente quando um comando retorna um código de saída diferente de zero (indicando erro). Por padrão, o Bash continua executando mesmo após um comando falhar, o que pode levar a comportamentos imprevisíveis e difíceis de depurar. Com set -e, qualquer erro interrompe o script, tornando-o mais previsível.

É importante notar que set -e não se aplica a comandos em condicionais (if, while, until) ou a comandos precedidos por ! (negação). Além disso, comandos em pipelines têm tratamento especial: apenas o último comando do pipe é verificado (a menos que set -o pipefail também esteja ativo).

Exemplo:

#!/bin/bash
set -e

echo "Este comando executa"
ls /diretorio/inexistente
echo "Este comando nunca será executado"

Neste exemplo, o script imprime a primeira mensagem, tenta listar um diretório que não existe (falha) e, como set -e está ativo, termina antes de imprimir a segunda mensagem.

set -u

A opção set -u (ou set -o nounset) trata o uso de variáveis não definidas como um erro, fazendo o script terminar. Por padrão, o Bash substitui variáveis não definidas por uma string vazia, o que pode mascarar erros de digitação ou lógica. Ativar set -u ajuda a detectar esses problemas precocemente.

É comum combinar set -u com set -e para maior robustez. No entanto, cuidado: se você tentar acessar uma variável que pode não estar definida em um contexto específico, o script falhará. Nesse caso, use expansão condicional como ${VAR:-default} para fornecer um valor padrão.

Exemplo:

#!/bin/bash
set -u

echo "Nome: $NOME"  # NOME não está definida, script termina aqui
echo "Isso não será impresso"

Ao executar, o Bash exibirá uma mensagem de erro como "NOME: unbound variable" e sairá.

set -o pipefail

Em um pipeline (comando1 | comando2 | comando3), o código de saída do pipeline é o do último comando. Se um comando intermediário falhar, o pipeline ainda pode ser considerado bem-sucedido se o último comando tiver sucesso. A opção set -o pipefail altera esse comportamento: o código de saída do pipeline será o do primeiro comando que falhar (ou zero se todos tiverem sucesso). Isso é crucial para detectar falhas em pipelines.

Normalmente, set -o pipefail é usado em conjunto com set -e para garantir que qualquer falha em qualquer etapa do pipeline interrompa o script.

Exemplo:

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

true | false | true  # false falha, então o pipeline falha
echo "Isso não será executado"

Sem pipefail, o pipeline acima teria sucesso (porque o último comando é true). Com pipefail, o pipeline falha devido ao false no meio.

set -x (debug)

A opção set -x (ou set -o xtrace) ativa um modo de depuração que exibe cada comando antes de executá-lo, com os argumentos expandidos. A saída é precedida por um sinal de mais (+) e pode ser útil para entender o fluxo do script e identificar onde algo dá errado.

Você pode ativar set -x no início do script ou em partes específicas usando set -x para ligar e set +x para desligar. Isso evita que a saída fique poluída com informações de debug em todo o script.

Exemplo:

#!/bin/bash
set -x  # Ativa debug

nome="João"
echo "Olá, $nome"
set +x  # Desativa debug

echo "Isso não será mostrado com +"

Ao executar, você verá algo como:

+ nome=João
+ echo 'Olá, João'
Olá, João
+ set +x
Isso não será mostrado com +

Boas práticas e observações finais

Recomenda-se iniciar scripts com set -euo pipefail para ativar as três opções de robustez. Isso garante que erros sejam detectados e o script pare, variáveis não definidas causem falha e pipelines reportem corretamente falhas intermediárias. O debug com set -x pode ser ativado condicionalmente, por exemplo, com uma variável de ambiente DEBUG.

Lembre-se de que set -e pode ser surpreendente em alguns casos, como comandos que retornam código diferente de zero em situações esperadas (por exemplo, grep que não encontra padrão). Nesses casos, use || true ou condicionais explicitamente.

Referências

Exercícios

  1. Crie um script que ative set -e e tente executar um comando que falha (ex: ls /naoexiste). Observe que o script termina antes de um comando posterior.
  2. ✓ Resposta:
    #!/bin/bash
    set -e
    echo "Antes do erro"
    ls /naoexiste
    echo "Depois do erro"  # Não executa
    
  3. Escreva um script que use set -u e tente imprimir uma variável não definida. O que acontece?
  4. ✓ Resposta:
    #!/bin/bash
    set -u
    echo "Valor: $NAODEFINIDA"  # Erro: unbound variable
    
  5. Crie um script com set -e e set -o pipefail que contenha um pipeline onde o comando do meio falhe. Mostre que o script termina.
  6. ✓ Resposta:
    #!/bin/bash
    set -e
    set -o pipefail
    true | false | true
    echo "Isso não será executado"
    
  7. Use set -x para depurar um script simples que define uma variável e imprime uma mensagem. Execute e veja a saída.
  8. ✓ Resposta:
    #!/bin/bash
    set -x
    nome="Maria"
    echo "Olá, $nome"
    set +x
    echo "Fim"
    
  9. Combine todas as opções (set -euo pipefail) em um script que tente ler um arquivo inexistente e depois imprima algo. Explique o comportamento.
  10. ✓ Resposta:
    #!/bin/bash
    set -euo pipefail
    cat /arquivo/inexistente | wc -l
    echo "Isso não será executado"