Shell Script do Zero

[34] Shell Script do Zero

Shell script do zero: shebang e permissão de execução, variáveis e substituição de comando, parâmetros posicionais, condicionais e laços, funções com escopo local, e o trio set -euo pipefail que separa um script de brinquedo de um script de produção.
DevOps

8 min de leitura

Até aqui foram aprendidos comandos que se executam um de cada vez. Shell script é o passo seguinte: reunir esses comandos em um arquivo que pode ser executado sempre que necessário, passado para outros, versionado no Git e integrado a pipelines de CI/CD.

Em DevOps, scripts de shell estão em toda parte — em rotinas de backup, em scripts de inicialização de containers, em hooks de deploy, em verificações de saúde de serviços. Não é necessário dominar todas as nuances do Bash para ser produtivo. É necessário dominar o suficiente para escrever scripts claros, seguros e confiáveis.

Estrutura Básica de um Script

Todo script Bash começa com o shebang — a linha que informa ao sistema qual interpretador deve executar o arquivo:

#!/bin/bash

Um script completo e mínimo:

#!/bin/bash

echo "Iniciando verificação do sistema..."
date
whoami
echo "Verificação concluída."

Salva-se o arquivo como verificacao.sh, concede-se permissão de execução e executa-se:

chmod +x verificacao.sh
./verificacao.sh

O ./ antes do nome indica ao shell que o script está no diretório atual. Sem ele, o shell buscaria o comando nos diretórios do $PATH e não o encontraria.

Variáveis

#!/bin/bash

AMBIENTE="producao"
VERSAO="1.4.2"
DATA=$(date +%Y-%m-%d)

echo "Deploy da versão $VERSAO no ambiente $AMBIENTE"
echo "Data: $DATA"

Alguns pontos importantes sobre variáveis no Bash:

  • Não se usa espaço em torno do = na atribuição
  • Para usar o valor, coloca-se $ antes do nome
  • Para capturar a saída de um comando, usa-se $(comando)
  • Por convenção, variáveis de ambiente e constantes são escritas em maiúsculas

Parâmetros de Entrada

Scripts podem receber argumentos ao serem executados:

#!/bin/bash

AMBIENTE=$1
VERSAO=$2

echo "Fazendo deploy da versão $VERSAO no ambiente $AMBIENTE"

Executando:

./deploy.sh producao 1.4.2

As variáveis especiais para parâmetros:

$1, $2, $3   # Primeiro, segundo, terceiro argumento
$@           # Todos os argumentos
$#           # Quantidade de argumentos
$0           # Nome do próprio script

Condicionais

#!/bin/bash

AMBIENTE=$1

if [ "$AMBIENTE" == "producao" ]; then
  echo "ATENÇÃO: deploy em produção. Confirmando..."
elif [ "$AMBIENTE" == "staging" ]; then
  echo "Deploy em staging. Prosseguindo."
else
  echo "Ambiente desconhecido: $AMBIENTE"
  exit 1
fi

O exit 1 encerra o script com código de erro — fundamental em pipelines de CI/CD, que interpretam qualquer código diferente de zero como falha.

Comparações numéricas usam operadores diferentes:

if [ $NUMERO -eq 0 ]; then   # igual
if [ $NUMERO -ne 0 ]; then   # diferente
if [ $NUMERO -gt 10 ]; then  # maior que
if [ $NUMERO -lt 10 ]; then  # menor que

Laços

#!/bin/bash

# Iterando sobre uma lista
for SERVIDOR in web01 web02 web03; do
  echo "Verificando $SERVIDOR..."
  # aqui entraria um ssh para cada servidor
done

# Iterando sobre arquivos
for ARQUIVO in /var/log/*.log; do
  echo "Processando: $ARQUIVO"
  wc -l "$ARQUIVO"
done

# Loop com contador
for i in $(seq 1 5); do
  echo "Tentativa $i..."
done

Funções

Funções organizam o script e evitam repetição:

#!/bin/bash

verificar_servico() {
  local SERVICO=$1
  if systemctl is-active --quiet "$SERVICO"; then
    echo "✓ $SERVICO está rodando"
  else
    echo "✗ $SERVICO está parado"
  fi
}

verificar_servico nginx
verificar_servico postgresql
verificar_servico redis

A palavra local restringe a variável ao escopo da função — boa prática para evitar conflitos com variáveis globais.

Tratamento de Erros

Um script profissional não silencia erros — ele os trata. Três configurações essenciais para scripts robustos:

#!/bin/bash

set -e          # Interrompe o script se qualquer comando falhar
set -u          # Trata variáveis não definidas como erro
set -o pipefail # Propaga erros em pipes

echo "Script iniciado"
comando_que_pode_falhar
echo "Esta linha só executa se o comando acima teve sucesso"

O set -e é especialmente importante em scripts de deploy — se um passo falha, o script para imediatamente em vez de continuar e causar danos maiores.

Um Script Real: Verificação de Saúde do Servidor

#!/bin/bash

set -euo pipefail

LOG="/var/log/health-check.log"
DATA=$(date "+%Y-%m-%d %H:%M:%S")

log() {
  echo "[$DATA] $1" | tee -a "$LOG"
}

verificar_disco() {
  USO=$(df / | awk 'NR==2 {print $5}' | tr -d '%')
  if [ "$USO" -gt 85 ]; then
    log "ALERTA: Disco em ${USO}% de uso"
  else
    log "OK: Disco em ${USO}% de uso"
  fi
}

verificar_memoria() {
  LIVRE=$(free -m | awk 'NR==2 {print $4}')
  log "INFO: Memória livre: ${LIVRE}MB"
}

verificar_servicos() {
  for SERVICO in nginx postgresql redis; do
    if systemctl is-active --quiet "$SERVICO"; then
      log "OK: $SERVICO rodando"
    else
      log "ERRO: $SERVICO parado"
    fi
  done
}

log "=== Início da verificação ==="
verificar_disco
verificar_memoria
verificar_servicos
log "=== Verificação concluída ==="

Este script pode ser executado manualmente ou agendado via cron para rodar a cada hora. Ele registra tudo em log e pode ser integrado a sistemas de alerta.

Referências para Aprofundamento

Documentação e leitura

Boas práticas

  • ShellCheck — Ferramenta online que analisa scripts Bash e aponta erros, más práticas e problemas de portabilidade. Recomenda-se passar todo script por aqui antes de colocar em produção.
  • Google Shell Style Guide — Guia de estilo do Google para scripts shell. Define convenções de nomenclatura, formatação e estrutura que tornam scripts mais legíveis e mantíveis.

Prática

  • Exercism — Bash Track — Exercícios progressivos de Bash com feedback de mentores. Excelente para solidificar a sintaxe praticando problemas reais.

Exercícios

Exercício 1

O que é o shebang e para que serve? E por que o script precisa ser chamado como ./verificacao.sh, e não apenas verificacao.sh?

Ver resposta

✓ Resposta: O shebang é a primeira linha, #!/bin/bash, e informa ao sistema qual interpretador deve executar aquele arquivo — sem ela, o kernel não sabe se o conteúdo é Bash, Python ou outra coisa. Já o ./ declara explicitamente que o arquivo está no diretório atual. Sem ele, o shell procura o nome apenas nos diretórios listados em $PATH, e o diretório atual não está lá. Essa ausência é deliberada: se . estivesse no $PATH, bastaria alguém deixar um arquivo chamado ls em uma pasta qualquer para que você o executasse sem perceber.

Exercício 2

Por que VERSAO = "1.4.2" falha no Bash, enquanto VERSAO="1.4.2" funciona? E o que DATA=$(date +%Y-%m-%d) guarda na variável?

Ver resposta

✓ Resposta: Porque no Bash o espaço separa o comando dos seus argumentos. VERSAO = "1.4.2" é lido como "execute o comando VERSAO passando = e 1.4.2 como argumentos" — daí o command not found. A atribuição exige o = colado dos dois lados. Já $(date +%Y-%m-%d) é substituição de comando: o date é executado primeiro e a variável recebe a saída dele, algo como 2025-03-10, e não o texto do comando.

Exercício 3

Para a chamada abaixo, diga o valor de $0, $1, $2, $@ e $#. Como $# costuma ser usado logo no início de um script?

./deploy.sh producao 1.4.2
Ver resposta

✓ Resposta: $0 é ./deploy.sh, o nome do próprio script; $1 é producao; $2 é 1.4.2; $@ são todos os argumentos, producao 1.4.2; e $# é 2, a quantidade. O $# costuma abrir o script como guarda de entrada: se a contagem não bate com o esperado, o script imprime o modo de uso e sai com exit 1 — antes de executar qualquer coisa com variáveis vazias.

Exercício 4

Por que a comparação de texto usa == e a de número usa -gt? E por que "$AMBIENTE" aparece entre aspas dentro do teste?

Ver resposta

✓ Resposta: São famílias distintas de operadores: == e != comparam texto, enquanto -eq, -ne, -gt e -lt comparam número. Não dá para usar > dentro de [ ], porque o shell o interpretaria como redirecionamento e criaria um arquivo em vez de comparar. As aspas resolvem outro problema: se $AMBIENTE estiver vazia ou contiver espaço, o teste sem aspas se desfaz em pedaços e vira erro de sintaxe. Com aspas, o valor chega inteiro, mesmo vazio.

Exercício 5

O que fazem set -e, set -u e set -o pipefail? Dê, para cada um, um estrago concreto que ele evita em um script de deploy.

Ver resposta

✓ Resposta: -e interrompe o script no primeiro comando que falha; sem ele, um passo quebrado não impede os seguintes, e o deploy publica um artefato incompleto como se tudo tivesse dado certo. -u trata variável não definida como erro; sem ele, um erro de digitação no nome vira string vazia silenciosa, e uma linha como rm -rf "$DIR_BUILD/" pode se transformar em rm -rf /. -o pipefail faz o pipe inteiro falhar se qualquer etapa falhar — por padrão só o último comando conta, então curl ... | tar -x "passa" mesmo quando o download quebrou no meio.

Exercício 6

Por que exit 1 é decisivo quando o script roda dentro de um pipeline de CI/CD? E qual o papel da palavra local dentro de uma função?

Ver resposta

✓ Resposta: O pipeline não lê a saída do script, lê o código de saída: 0 significa sucesso e qualquer outro valor significa falha. Sem exit 1, um script que detectou um problema ainda assim termina com 0, e o pipeline segue adiante convencido de que deu tudo certo — a falha silenciosa, que só aparece em produção. Já o local restringe a variável ao escopo da função: sem ele, SERVICO=$1 sobrescreve qualquer variável global de mesmo nome, criando um bug que não se manifesta enquanto a função é chamada uma vez só, e explode quando ela passa a ser reutilizada.

Comentários

Mais em DevOps

Cultura DevOps: Maturidade, Postmortems e Melhoria Contínua
Cultura DevOps: Maturidade, Postmortems e Melhoria Contínua

O que separa uma organização DevOps de alto desempenho de uma medíocre depois…

Semantic Versioning e Tags de Release
Semantic Versioning e Tags de Release

Semantic Versioning e tags de release: o que MAJOR, MINOR e PATCH comunicam…

O Que é um Container e Por Que Isso Mudou Tudo
O Que é um Container e Por Que Isso Mudou Tudo

O que é um container e por que ele mudou a entrega de software: o problema…