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! 😆


Referências