Modo Estrito do Bash: set -euo pipefail Explicado
O Que É o Modo Estrito do Bash?
Conclusão: Coloque
set -euo pipefailno início de um script para capturar erros, variáveis não definidas e falhas em pipes imediatamente. O objetivo é revelar bugs ocultos cedo.
Por padrão, o bash é permissivo demais. Um comando pode falhar, uma variável pode estar indefinida, ou um pipe pode quebrar no meio, e o script continua executando. O resultado é o acidente clássico: "falhou no meio, mas executou até o final e deixou dados corrompidos para trás."
O modo estrito do bash é o idioma bem conhecido de habilitar três opções no início de um script para mudar o bash para o comportamento "parar em caso de falha".
#!/usr/bin/env bash set -euo pipefail IFS=$'\n\t'
As quatro partes abordadas aqui
set -e(errexit): sair imediatamente em caso de falha de comandoset -u(nounset): tratar referências a variáveis não definidas como errosset -o pipefail: capturar falhas dentro de um pipelineIFS=$'\n\t': prevenir acidentes de divisão de palavras (opcional, mas recomendado)
Premissas (ambiente alvo)
- Shell: bash (
#!/usr/bin/env bash) pipefailé uma extensão do bash. Não funciona em POSIXsh(dash, etc.)- Destinado a scripts, não shells interativos
Por Que o Bash Padrão É Perigoso?
Conclusão: O bash padrão continua após uma falha de comando e trata variáveis digitadas errado como strings vazias. Falhas são ignoradas enquanto o processamento continua, e é aí que os acidentes acontecem.
Observe o script abaixo. Ele entra em um diretório de backup e exclui arquivos antigos, um padrão muito comum.
#!/usr/bin/env bash cd "$BACKUP_DIR" rm -rf ./*
Se você esquecer de definir BACKUP_DIR, o bash padrão executa cd "" (que não faz nada e permanece no diretório atual), e rm -rf ./* executa no diretório em que você já está. Como cd "" tem sucesso (não faz nada) sem gerar um erro, você apaga um lugar que nunca pretendia.
Com o modo estrito, esse acidente é bloqueado duplamente.
set -upara o script na referência a$BACKUP_DIRnão definida- Se
$BACKUP_DIRaponta para um caminho inexistente,set -esai imediatamente quandocdfalha (uma string vaziacd ""tem sucesso, então o caso de string vazia é tratado porset -u)
"Continuar para a próxima linha mesmo em caso de falha" é o comportamento padrão do bash. O modo estrito existe para cortar essa continuação silenciosa.
O Que set -e (errexit) Faz, e Onde Falha?
Conclusão:
set -esai do script no momento em que um comando retorna diferente de zero. Mas existem muitos contextos onde ele não dispara (condiçõesif,&&e mais), então não dependa excessivamente dele.
set -e encerra o script assim que um comando retorna um status de saída diferente de zero.
set -e cp important.conf /etc/myapp/ # para aqui se falhar systemctl restart myapp # executa apenas se o anterior teve sucesso
Onde errexit NÃO se aplica
set -e tem contextos documentados onde não tem efeito. Não conhecê-los leva ao acidente "deveria ter parado, mas não parou".
- A condição de
if cmd; then ...(a falha é usada para o teste) - O lado esquerdo de
cmd && .../cmd || ...(qualquer coisa exceto o último comando) - Um comando negado com
! - Todo comando em um pipeline exceto o último (coberto pelo
pipefail)
set -e
# NAO para mesmo se grep falhar (e uma condicao)
if grep -q pattern file.txt; then
echo "found"
fi
# Para tolerar intencionalmente uma falha, torne explicito com || true
risky_command || trueNão dependa de set -e sozinho. Verifique explicitamente o status de saída de etapas críticas. Trate set -e como uma rede de segurança para falhas que você acidentalmente negligenciou.
Os Papéis de set -u e pipefail
Conclusão:
set -utransforma referências a variáveis não definidas em erros para capturar erros de digitação.set -o pipefailcaptura falhas no meio do pipe para que combinações comogrep | sortrelatem sucesso ou falha corretamente.
set -u (nounset)
Referenciar uma variável indefinida se torna um erro e para o script, permitindo que você identifique erros de digitação em nomes de variáveis instantaneamente.
set -u name="penguin" echo "$nmae" # erro de digitacao -> sai com "unbound variable"
Quando você intencionalmente quer "usar um padrão se não definida", torne explícito com expansão de parâmetros.
# Usar um padrao sem erro quando nao definida
echo "${OPTIONAL_VAR:-default}"
# Mesmo para parametros posicionais (uma protecao quando $1 esta ausente)
target="${1:-/tmp}"set -o pipefail
Por padrão, o status de saída de um pipeline é o do último comando. Mesmo se um comando anterior falhar, o pipe inteiro é reportado como sucesso quando o último tem sucesso.
# Sem pipefail: $? e 0 se grep tem sucesso, mesmo quando curl falha curl -s https://example.com/data | grep "key" set -o pipefail # Com pipefail: a falha do curl se propaga como uma falha do pipe curl -s https://example.com/data | grep "key"
pipefail retorna o status de saída do comando mais à direita que falhou no pipeline, ou 0 se todos tiverem sucesso. É especialmente valioso ao encadear busca e filtragem de dados.
Por Que Configurar o IFS É Recomendado?
Conclusão:
IFS=$'\n\t'limita os separadores de divisão de palavras apenas a nova linha e tabulação, prevenindo divisões não intencionadas em espaços em nomes de arquivos e caminhos.
IFS (Internal Field Separator) é o conjunto de caracteres que o bash usa para dividir strings em palavras. O padrão é espaço, tabulação e nova linha. O espaço é o que causa acidentes com nomes de arquivos que contêm espaços.
# IFS padrao: "my file.txt" se divide em duas palavras no espaco
for f in $(ls); do
echo "$f"
done
# IFS=$'\n\t': dividir apenas em nova linha e tabulacao
IFS=$'\n\t'O "modo estrito não oficial do bash" de Aaron Maxwell recomenda essa configuração de IFS junto com set -euo pipefail. Remover o espaço dos separadores faz a divisão em for-loops e expansões se comportar de forma mais intuitiva.
Alterar IFS tem efeitos colaterais. Se algum código depende de divisão baseada em espaço (como ler arrays com read -a), restaure IFS localmente para aquela seção ou pule a configuração ali.
Um Template Prático de Modo Estrito
Conclusão: Mantenha um boilerplate que combina o shebang, modo estrito e uma trap de erro para que você sempre possa iniciar um script seguro com esforco mínimo.
Um template que você pode usar como está. Mostrar a linha que falhou com trap torna a depuração dramaticamente mais fácil.
#!/usr/bin/env bash
#
# Esqueleto de script seguro
#
set -euo pipefail
IFS=$'\n\t'
# Imprimir o numero da linha em caso de erro
trap 'echo "Error on line $LINENO" >&2' ERR
main() {
local target="${1:-/tmp}"
echo "Target: $target"
# logica principal vai aqui
}
main "$@"Copiar e colar: forma mínima
#!/usr/bin/env bash set -euo pipefail IFS=$'\n\t'
Por que usar `#!/usr/bin/env bash`?
Escrever #!/bin/bash diretamente quebra em sistemas onde o bash está instalado fora de /bin (por exemplo /usr/local/bin). Usar env busca o bash no PATH, melhorando a portabilidade. Como pipefail é específico do bash, também é importante nomear o bash explicitamente em vez de #!/bin/sh.
Ressalvas do Modo Estrito em um Relance
Conclusão: O modo estrito não é uma solução mágica. Use-o sabendo os contextos onde
set -enão se aplica, os efeitos colaterais doIFSe o risco de aplicá-lo retroativamente em scripts existentes.
| Item | Ressalva |
|---|---|
set -e |
Não tem efeito em condições if, lados esquerdos de && e mais |
set -u |
Torne padrões explícitos com ${VAR:-default} |
pipefail |
Apenas bash. Não disponível em POSIX sh |
IFS=$'\n\t' |
Cuidado com efeitos colaterais se o código depende de divisão por espaço |
| Retroativo | Adicioná-lo a um script antigo revela todos os bugs previamente ocultos |
O que não fazer
- Usar
pipefailem um script#!/bin/sh - Referenciar
$1sem verificação sobset -u - Adicionar modo estrito a um script grande existente sem testar