Deixando o bash mais rigoroso usando set -euo pipefail
Tabela de Conteúdo
Bash é uma das linguagens mais utilizadas para automatizar tarefas em sistemas Linux e Unix. No entanto é necessario se atentar a um ponto crucial que, diferente de muitas linguagens ele não interrompe a execução do script após uma falha.
Neste exemplo abaixo, o código continua sendo executado mesmo depois de ocorrer um erro:
#!/bin/bash
mkdir /tmp/build
cp artefato.tar.gz /tmp/build/
tar -xzf /tmp/build/artefato.tar.gz
echo "deploy concluído"
O script chega até o echo sem sinal de erro (considerando que o artefato.tar.gz exista). No final, isso pode significar um deploy com artefato corrompido ou ausente.
O comando set
O comando set permite alterar os valores das opções do shell e definir os parâmetros posicionais, ou exibir os nomes e valores das variáveis do shell.
A sintaxe de uso é bem simples:
set [opções] [argumentos]
Existem muitas outras opções dísponiveis que podemos pode verificar aqui, mas neste post abordaremos somente as opções -e, -u e -o pipefail.
Entendendo a opção -e
Encerra o script imediatamente se qualquer comando retornar código de saída diferente de zero:
#!/bin/bash
set -e
mkdir /tmp/build
cp artefato.tar.gz /tmp/build/ # script para aqui se falhar
tar -xzf /tmp/build/artefato.tar.gz
echo "deploy concluído"
Um detalhe importante: set -e não intercepta falhas dentro de condicionais:
set -e
if cp artefato.tar.gz /tmp/build/; then # falha aqui não encerra o script
echo "copiado"
fi
Isso é comportamento esperado, pois o bash trata o código de saída do if como parte do fluxo de controle.
Entendendo a opção -u
Trata variáveis indefinidas como erro, encerrando o script:
#!/bin/bash
set -u
echo "deploy em: $AMBIENTE" # encerra se AMBIENTE não estiver definida
Sem -u, $AMBIENTE retornaria string vazia e o script continuaria — potencialmente executando comandos com parâmetros incorretos.
Para variáveis opcionais, use expansão com valor padrão:
AMBIENTE=${AMBIENTE:-staging} # usa "staging" se AMBIENTE não estiver definida
O Comando set -o pipefail
Por padrão, o código de saída de um pipeline é o do último comando — mesmo que um comando anterior tenha falhado:
cat arquivo_inexistente.txt | grep "erro" | wc -l
echo $? # retorna 0, pois wc -l teve sucesso
Com pipefail, o código de saída do pipeline passa a ser o do primeiro comando que falhou:
#!/bin/bash
set -o pipefail
cat arquivo_inexistente.txt | grep "erro" | wc -l
# script encerra aqui com código de saída do cat
Combinando as três flags
A forma mais comum é declarar tudo na primeira linha após o shebang:
#!/bin/bash
set -euo pipefail
Equivale a:
set -e # encerra em erro
set -u # variável indefinida é erro
set -o pipefail # falha em pipeline é erro
Tratando erros de forma controlada
Há situações em que você quer capturar o erro sem encerrar o script. Use || true para ignorar intencionalmente:
#!/bin/bash
set -euo pipefail
# ignora erro se o container já não existe
docker rm meu-container || true
# captura código de saída sem encerrar
if ! cp artefato.tar.gz /tmp/build/; then
echo "falha ao copiar artefato" >&2
exit 1
fi
Para debug, adicione set -x para imprimir cada comando antes de executar:
#!/bin/bash
set -euo pipefail
set -x # Utilize somente enquanto for debug
Exemplo completo
#!/bin/bash
set -euo pipefail
BUILD_DIR=${BUILD_DIR:-/tmp/build}
ARTEFATO=${1:?"uso: $0 <artefato.tar.gz>"} # encerra com mensagem se $1 não informado
echo "preparando ambiente em $BUILD_DIR"
rm -rf "$BUILD_DIR"
mkdir -p "$BUILD_DIR"
echo "extraindo artefato"
tar -xzf "$ARTEFATO" -C "$BUILD_DIR"
echo "deploy concluído em $BUILD_DIR"
Com ${1:?"mensagem"}, o script falha imediatamente com uma mensagem clara se o argumento não for passado — sem precisar de if explícito.
Simples assim! 😆