Opções do set para scripts robustos
Esta aula aborda as opções do comando set que tornam scripts bash mais robustos e seguros, incluindo set -e, set -u, set -o pipefail e set -x. Você aprenderá a evitar falhas silenciosas, detectar variáveis não definidas, capturar erros em pipelines e depurar scripts de forma eficiente.
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
- GNU Bash Manual: The Set Builtin
- DevHints: Bash scripting cheatsheet
- ShellCheck: Static analysis tool for shell scripts
- Google Shell Style Guide
- Advanced Bash-Scripting Guide: Options
Exercícios
-
Escreva um script que use
set -eeset -u. Defina uma variávelarquivoe tente exibi-la. Em seguida, tente exibir uma variávelinexistente. O que acontece?✓ Resposta:O script falha ao tentar exibir
$inexistenteporqueset -utrata 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 linhaecho $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 -
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 comset -e.✓ Resposta:Com
set -o pipefaileset -e, o pipeline falha e o script termina. O comandols /inexistentegera 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" -
Use
set -xpara depurar um script que calcula a soma de dois números passados como argumentos. Mostre a saída tracejada.✓ Resposta:O script com
set -ximprime 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 -
Explique a diferença entre
set -eeset -u. Dê um exemplo ondeset -enão pegaria um erro, masset -usim.✓ Resposta:set -einterrompe o script quando um comando retorna status não-zero.set -uinterrompe quando uma variável não definida é expandida. Um erro queset -enã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$nomecausaria erro. Portanto,set -udetecta variáveis não definidas queset -eignora. -
Crie um script que combine
set -euo pipefaile 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,
cpsem permissão), o script para imediatamente. O uso deset -ugarante que$TMPDIResteja definida.pipefailnão é usado aqui, mas está incluído para consistência.