Modo Estrito do Bash: set -euo pipefail Explicado
O Que E o Modo Estrito do Bash?
Conclusao: Coloque
set -euo pipefailno inicio de um script para capturar erros, variaveis nao definidas e falhas em pipes imediatamente. O objetivo e revelar bugs ocultos cedo.
Por padrao, o bash e permissivo demais. Um comando pode falhar, uma variavel pode estar indefinida, ou um pipe pode quebrar no meio, e o script continua executando. O resultado e o acidente classico: "falhou no meio, mas executou ate o final e deixou dados corrompidos para tras."
O modo estrito do bash e o idioma bem conhecido de habilitar tres opcoes no inicio 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 referencias a variaveis nao definidas como errosset -o pipefail: capturar falhas dentro de um pipelineIFS=$'\n\t': prevenir acidentes de divisao de palavras (opcional, mas recomendado)
Premissas (ambiente alvo)
- Shell: bash (
#!/usr/bin/env bash) pipefaile uma extensao do bash. Nao funciona em POSIXsh(dash, etc.)- Destinado a scripts, nao shells interativos
Por Que o Bash Padrao E Perigoso?
Conclusao: O bash padrao continua apos uma falha de comando e trata variaveis digitadas errado como strings vazias. Falhas sao ignoradas enquanto o processamento continua, e e ai que os acidentes acontecem.
Observe o script abaixo. Ele entra em um diretorio de backup e exclui arquivos antigos, um padrao muito comum.
#!/usr/bin/env bash cd "$BACKUP_DIR" rm -rf ./*
Se voce esquecer de definir BACKUP_DIR, o bash padrao executa cd "" (que nao faz nada e permanece no diretorio atual), e rm -rf ./* executa no diretorio em que voce ja esta. Como cd "" tem sucesso (nao faz nada) sem gerar um erro, voce apaga um lugar que nunca pretendia.
Com o modo estrito, esse acidente e bloqueado duplamente.
set -upara o script na referencia a$BACKUP_DIRnao definida- Se
$BACKUP_DIRaponta para um caminho inexistente,set -esai imediatamente quandocdfalha (uma string vaziacd ""tem sucesso, entao o caso de string vazia e tratado porset -u)
"Continuar para a proxima linha mesmo em caso de falha" e o comportamento padrao do bash. O modo estrito existe para cortar essa continuacao silenciosa.
O Que set -e (errexit) Faz, e Onde Falha?
Conclusao:
set -esai do script no momento em que um comando retorna diferente de zero. Mas existem muitos contextos onde ele nao dispara (condicoesif,&&e mais), entao nao dependa excessivamente dele.
set -e encerra o script assim que um comando retorna um status de saida 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 NAO se aplica
set -e tem contextos documentados onde nao tem efeito. Nao conhece-los leva ao acidente "deveria ter parado, mas nao parou".
- A condicao de
if cmd; then ...(a falha e usada para o teste) - O lado esquerdo de
cmd && .../cmd || ...(qualquer coisa exceto o ultimo comando) - Um comando negado com
! - Todo comando em um pipeline exceto o ultimo (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 || trueNao dependa de set -e sozinho. Verifique explicitamente o status de saida de etapas criticas. Trate set -e como uma rede de seguranca para falhas que voce acidentalmente negligenciou.
Os Papeis de set -u e pipefail
Conclusao:
set -utransforma referencias a variaveis nao definidas em erros para capturar erros de digitacao.set -o pipefailcaptura falhas no meio do pipe para que combinacoes comogrep | sortrelatem sucesso ou falha corretamente.
set -u (nounset)
Referenciar uma variavel indefinida se torna um erro e para o script, permitindo que voce identifique erros de digitacao em nomes de variaveis instantaneamente.
set -u name="penguin" echo "$nmae" # erro de digitacao -> sai com "unbound variable"
Quando voce intencionalmente quer "usar um padrao se nao definida", torne explicito com expansao de parametros.
# 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 padrao, o status de saida de um pipeline e o do ultimo comando. Mesmo se um comando anterior falhar, o pipe inteiro e reportado como sucesso quando o ultimo 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 saida do comando mais a direita que falhou no pipeline, ou 0 se todos tiverem sucesso. E especialmente valioso ao encadear busca e filtragem de dados.
Por Que Configurar o IFS E Recomendado?
Conclusao:
IFS=$'\n\t'limita os separadores de divisao de palavras apenas a nova linha e tabulacao, prevenindo divisoes nao intencionadas em espacos em nomes de arquivos e caminhos.
IFS (Internal Field Separator) e o conjunto de caracteres que o bash usa para dividir strings em palavras. O padrao e espaco, tabulacao e nova linha. O espaco e o que causa acidentes com nomes de arquivos que contem espacos.
# 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 nao oficial do bash" de Aaron Maxwell recomenda essa configuracao de IFS junto com set -euo pipefail. Remover o espaco dos separadores faz a divisao em for-loops e expansoes se comportar de forma mais intuitiva.
Alterar IFS tem efeitos colaterais. Se algum codigo depende de divisao baseada em espaco (como ler arrays com read -a), restaure IFS localmente para aquela secao ou pule a configuracao ali.
Um Template Pratico de Modo Estrito
Conclusao: Mantenha um boilerplate que combina o shebang, modo estrito e uma trap de erro para que voce sempre possa iniciar um script seguro com esforco minimo.
Um template que voce pode usar como esta. Mostrar a linha que falhou com trap torna a depuracao dramaticamente mais facil.
#!/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 minima
#!/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 esta instalado fora de /bin (por exemplo /usr/local/bin). Usar env busca o bash no PATH, melhorando a portabilidade. Como pipefail e especifico do bash, tambem e importante nomear o bash explicitamente em vez de #!/bin/sh.
Ressalvas do Modo Estrito em um Relance
Conclusao: O modo estrito nao e uma solucao magica. Use-o sabendo os contextos onde
set -enao se aplica, os efeitos colaterais doIFSe o risco de aplica-lo retroativamente em scripts existentes.
| Item | Ressalva |
|---|---|
set -e |
Nao tem efeito em condicoes if, lados esquerdos de && e mais |
set -u |
Torne padroes explicitos com ${VAR:-default} |
pipefail |
Apenas bash. Nao disponivel em POSIX sh |
IFS=$'\n\t' |
Cuidado com efeitos colaterais se o codigo depende de divisao por espaco |
| Retroativo | Adiciona-lo a um script antigo revela todos os bugs previamente ocultos |
O que nao fazer
- Usar
pipefailem um script#!/bin/sh - Referenciar
$1sem verificacao sobset -u - Adicionar modo estrito a um script grande existente sem testar