Shell Script Avançado: Funções, Arrays, Tratamento de Erros e Boas Práticas
Scripts Bash são a cola da administração de sistemas Linux. Mas existe uma diferença enorme entre um script que funciona uma vez e um script que pode rodar em produção às 3h da manhã sem explodir o servidor. Este artigo cobre as técnicas que fazem essa diferença.
O Cabeçalho Obrigatório de Todo Script Sério
#!/usr/bin/env bash
set -euo pipefail
# -e : sai imediatamente se qualquer comando retornar erro
# -u : trata variáveis não definidas como erro (evita $VAZIO virar "")
# -o pipefail : propaga erro em pipes (cmd1 | cmd2 → falha se cmd1 falha)
Sem esses três flags, seu script continua em silêncio após um rm -rf que falhou ou uma variável vazia que destrói um caminho de arquivo.
Funções
#!/usr/bin/env bash
set -euo pipefail
# Convenção: funções de log com timestamp
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] INFO: $*"; }
warn() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] WARN: $*" >&2; }
die() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERRO: $*" >&2; exit 1; }
# Função com retorno de valor
soma() {
local a=$1 b=$2 # local: variável não vaza para o escopo global
echo $(( a + b ))
}
resultado=$(soma 3 7)
log "3 + 7 = $resultado"
# Função que verifica dependências
requer() {
for cmd in "$@"; do
command -v "$cmd" &>/dev/null || die "Comando não encontrado: $cmd"
done
}
requer curl jq aws
Arrays
#!/usr/bin/env bash
set -euo pipefail
# Array indexado
servidores=("web01" "web02" "db01" "cache01")
# Iterar
for srv in "${servidores[@]}"; do
echo "Verificando $srv..."
done
# Número de elementos
echo "Total: ${#servidores[@]}"
# Slice (índices 1 e 2)
echo "${servidores[@]:1:2}" # → web02 db01
# Array associativo (dicionário)
declare -A config
config[host]="localhost"
config[porta]="5432"
config[banco]="producao"
for chave in "${!config[@]}"; do
echo "$chave = ${config[$chave]}"
done
# Ler linhas de um arquivo para array
mapfile -t linhas < /etc/hosts
echo "Linhas em /etc/hosts: ${#linhas[@]}"
Processando Argumentos com getopts
#!/usr/bin/env bash
set -euo pipefail
uso() {
cat <<EOF
Uso: $0 [-h] [-v] [-e AMBIENTE] [-n NOME]
-h Mostra esta ajuda
-v Modo verbose
-e AMBIENTE Ambiente (dev|stg|prod) [obrigatório]
-n NOME Nome do deploy [padrão: app]
EOF
exit 1
}
VERBOSE=0
AMBIENTE=""
NOME="app"
while getopts ":hve:n:" opt; do
case $opt in
h) uso ;;
v) VERBOSE=1 ;;
e) AMBIENTE=$OPTARG ;;
n) NOME=$OPTARG ;;
:) die "Opção -$OPTARG requer argumento" ;;
?) die "Opção inválida: -$OPTARG" ;;
esac
done
[[ -z "$AMBIENTE" ]] && die "-e AMBIENTE é obrigatório"
[[ "$AMBIENTE" =~ ^(dev|stg|prod)$ ]] || die "Ambiente inválido: $AMBIENTE"
echo "Deploy de '$NOME' em '$AMBIENTE'"
[[ $VERBOSE -eq 1 ]] && echo "[verbose] Detalhes adicionais aqui"
Tratamento de Erros com trap
#!/usr/bin/env bash
set -euo pipefail
TMPDIR=$(mktemp -d)
# Cleanup automático ao sair (com erro ou não)
cleanup() {
local codigo=$?
rm -rf "$TMPDIR"
[[ $codigo -ne 0 ]] && echo "Script falhou com código $codigo" >&2
}
trap cleanup EXIT
# Se o script morrer por sinal (Ctrl+C, kill), limpar antes
trap 'echo "Interrompido" >&2; exit 130' INT TERM
echo "Arquivos temporários em $TMPDIR"
# ... seu trabalho aqui ...
# TMPDIR é removido automaticamente ao sair
Logging para Arquivo
#!/usr/bin/env bash
set -euo pipefail
LOGFILE="/var/log/meu-script.log"
LOGDIR=$(dirname "$LOGFILE")
mkdir -p "$LOGDIR"
# Redirecionar stdout e stderr para o log E para o terminal
exec > >(tee -a "$LOGFILE") 2>&1
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"; }
log "=== Iniciando script $(basename "$0") ==="
log "Usuário: $(whoami) | PID: $$"
Verificações de Segurança Essenciais
# Verificar se está rodando como root
[[ $EUID -eq 0 ]] || die "Este script precisa ser root"
# Verificar se arquivo existe antes de usar
[[ -f "$CONFIG" ]] || die "Arquivo não encontrado: $CONFIG"
# Evitar race condition com lock file
LOCKFILE="/tmp/meu-script.lock"
exec 200>"$LOCKFILE"
flock -n 200 || die "Outra instância já está rodando"
# Validar que variável não está vazia E é numérica
[[ "${PORTA:-}" =~ ^[0-9]+$ ]] || die "PORTA deve ser numérica"
# Aspas duplas SEMPRE em variáveis (evita word splitting)
arquivo="meu arquivo com espaços.txt"
rm -f "$arquivo" # correto
# rm -f $arquivo # errado: vira 'rm -f meu arquivo com espaços.txt' → 4 args
Template de Script de Produção
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
LOGFILE="/var/log/$(basename "$0" .sh).log"
exec > >(tee -a "$LOGFILE") 2>&1
log() { echo "[$(date '+%F %T')] INFO $*"; }
warn() { echo "[$(date '+%F %T')] WARN $*" >&2; }
die() { echo "[$(date '+%F %T')] ERRO $*" >&2; exit 1; }
cleanup() { log "Finalizado (código: $?)"; }
trap cleanup EXIT
main() {
log "Iniciando — args: $*"
# seu código aqui
}
main "$@"
Este template já tem logging, cleanup automático, propagação de erro e passagem de argumentos para a função main. Copie, adapte e use como base para qualquer script novo.