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
- Bash Reference Manual — gnu.org — A documentação oficial e completa do Bash. Seções 3 e 4 cobrem sintaxe, variáveis e controle de fluxo.
- Shell Scripting Tutorial — shellscript.sh — Guia gratuito e objetivo, organizado por tópicos. Bom ponto de partida para quem quer ir além do básico.
- Advanced Bash-Scripting Guide — tldp.org — Referência extensa para quem quiser aprofundar. Cobre desde variáveis até expressões regulares e manipulação de strings.
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.