Linux: Script Bash Avançado
Scripts básicos resolvem tarefas simples, mas scripts de nível produção precisam de código bem mais robusto — funções para dividir a lógica, tratamento de erros para evitar "falhas silenciosas", e parsing de parâmetros para tornar os scripts mais fáceis de usar.
📋 Pré-requisitos: Você já deve conhecer
- Aula 20: Fundamentos de Script Bash
1. O Que Você Vai Aprender
- Definição de funções e valores de retorno
- Arrays indexados e associativos
- Tratamento de erros com
set -eetrap - Parâmetros de linha de comando com
getopts - Técnicas de debugging com
set -x
2. A História do Script do Xiaoming Que Sumiu no Servidor
(1) O Problema: O Script Continua Rodando Mesmo Depois dos Erros
O Xiaoming escreveu um script de deploy:
#!/bin/bash
cd /var/www/myapp
git pull origin main
npm install # ❌ Falhou
npm run build # Mas o script continuou rodando
pm2 restart app # Reiniciou com código antigo e build quebrado
(2) Adicionando Uma Linha Para Deixar Tudo Seguro
O Bob disse: "Coloca set -e lá no topo — ele para em qualquer erro."
#!/bin/bash
set -e # Para ao primeiro erro
cd /var/www/myapp
git pull origin main
npm install
npm run build
pm2 restart app
(3) O Benefício: Segurança em Primeiro Lugar
Com set -e, se o npm install falhar, o script sai na hora em vez de reiniciar o serviço com código quebrado.
3. Pontos-Chave
(1) Funções
# Definindo uma função
greet() {
echo "Hello, $1!"
echo "Today is $(date)"
}
# Sintaxe alternativa
function greet {
echo "Hello, $1!"
}
# Chamando a função
greet "Alice"
# Variáveis locais
myfunc() {
local x=10 # Variável local, não afeta o escopo externo
echo "Inside: $x"
}
x=5
myfunc # Saída: Inside: 10
echo "Outside: $x" # Saída: Outside: 5
(2) Arrays
# Array indexado
fruits=("apple" "banana" "orange")
fruits[3]="grape" # Adicionando elemento
echo ${fruits[0]} # apple
echo ${fruits[@]} # Todos os elementos
echo ${#fruits[@]} # Tamanho do array
# Iterando sobre o array
for fruit in "${fruits[@]}"; do
echo "$fruit"
done
# Array associativo (como um dicionário)
declare -A config
config[host]="localhost"
config[port]="8080"
config[user]="admin"
echo ${config[host]} # localhost
(3) Tratamento de Erros
# set -e: Sai em caso de erro
set -e
# set -u: Erro em variável indefinida
set -u
# set -o pipefail: Pipeline falha se qualquer comando falhar
set -o pipefail
# Combinação comum
set -euo pipefail
> 💡 Dica: `set -euo pipefail` é o "trio de segurança" dos scripts de produção — `-e` para em qualquer erro, `-u` pega variáveis indefinidas, `-o pipefail` faz as falhas do pipeline se propagarem. Todo script não trivial deve incluir essa linha.
# trap: Executa limpeza ao receber sinal ou ao sair
trap 'echo "Script interrupted"; exit 1' INT TERM
trap 'rm -f /tmp/tempfile; echo "Cleanup done"' EXIT
> 💡 Dica: O `trap` consegue executar limpeza automaticamente quando o script termina — seja em finalização normal ou interrompido com Ctrl+C, o `trap EXIT` é acionado. Usos comuns: apagar arquivos temporários, liberar locks de arquivo, restaurar configurações. Coloque `trap 'rm -f $TMPFILE' EXIT` no topo e você não precisa se preocupar com limpeza manual em cada ponto de saída.
# Handler customizado de erro
error_handler() {
echo "❌ Error on line $1"
echo "Command: $2"
exit 1
}
trap 'error_handler $LINENO "$BASH_COMMAND"' ERR
(4) getopts — Parsing de Parâmetros
# Sintaxe do getopts
while getopts "f:o:hv" opt; do
case $opt in
f) INPUT_FILE="$OPTARG" ;; # -f nome_do_arquivo
o) OUTPUT_DIR="$OPTARG" ;; # -o saída
h) show_help; exit 0 ;; # -h ajuda
v) VERBOSE=true ;; # -v verbose
*) echo "Unknown option"; exit 1 ;;
esac
done
> ℹ️ Nota: O `getopts` é o parser de parâmetros nativo do bash — na string de opções `"f:o:hv"`, os dois-pontos `:` indicam que a opção exige um argumento (ex.: `-f nome_do_arquivo`), sem dois-pontos significa que é uma flag (ex.: `-v`). Ele não aceita opções longas (como `--help`); para isso use o comando externo `getopt` ou faça parsing manual.
(5) Técnicas de Debugging
# Modo debug: mostra cada comando na medida em que executa
set -x
# Desliga o debugging
set +x
# Liga o debugging só num trecho específico
echo "Normal execution"
set -x
echo "This section is traced"
for i in 1 2 3; do echo "$i"; done
set +x
echo "Back to normal"
# Ou já chama o script em modo debug
# bash -x script.sh
(6) ▶ Exemplo: Encapsulamento em Funções
#!/bin/bash
# functions-demo.sh - Exemplos de funções
log() {
local level="${1:-INFO}"
local message="$2"
echo "[$(date '+%H:%M:%S')] [$level] $message"
}
die() {
log "ERROR" "$1"
exit 1
}
check_dependency() {
if ! command -v "$1" &> /dev/null; then
die "Required command '$1' not found"
fi
}
# Usando as funções
log "INFO" "Starting dependency check..."
check_dependency "git"
check_dependency "node"
log "INFO" "Dependency check passed"
(7) ▶ Exemplo: Operações com Arrays
#!/bin/bash
# array-demo.sh - Exemplos de arrays
# Lista de servidores
servers=("web01" "web02" "db01" "cache01")
echo "Server count: ${#servers[@]}"
echo "--- Iterating servers ---"
for server in "${servers[@]}"; do
echo "Checking: $server"
done
echo "--- Associative array example ---"
declare -A disk_usage
disk_usage[/]="45%"
disk_usage[/home]="62%"
disk_usage[/var]="78%"
for mount in "${!disk_usage[@]}"; do
echo "$mount: ${disk_usage[$mount]}"
done
Saída:
TEXT[10:23:01] [INFO] Starting dependency check... [10:23:01] [INFO] Dependency check passed Server count: 4 --- Iterating servers --- Checking: web01 Checking: web02 Checking: db01 Checking: cache01 --- Associative array example --- /: 45% /home: 62% /var: 78%
(8) ▶ Exemplo: Tratamento de Erros com set -e / trap
#!/bin/bash
# safe-script.sh - Script robusto
set -euo pipefail
cleanup() {
echo "Cleaning temporary files..."
rm -f /tmp/temp_*.txt
echo "Done"
}
error_handler() {
echo "❌ Error occurred on line $1"
cleanup
exit 1
}
trap error_handler ERR
trap cleanup EXIT
echo "Starting execution..."
mkdir -p /tmp/test
touch /tmp/test/file.txt
cat /tmp/test/file.txt
# Se o cat falhar num arquivo inexistente, o script para e dispara o trap ERR
Saída:
TEXTStarting execution... Cleaning temporary files... Done
(9) ▶ Exemplo: Parsing de Parâmetros com getopts
#!/bin/bash
# backup-tool.sh - Ferramenta de backup com parsing de parâmetros
usage() {
echo "Usage: $0 -s <source_dir> -d <dest_dir> [-c] [-v]"
echo " -s Source directory to back up"
echo " -d Backup destination directory"
echo " -c Compress backup"
echo " -v Verbose output"
echo " -h Show help"
exit 0
}
# Valores padrão
COMPRESS=false
VERBOSE=false
while getopts "s:d:cvh" opt; do
case $opt in
s) SOURCE="$OPTARG" ;;
d) DEST="$OPTARG" ;;
c) COMPRESS=true ;;
v) VERBOSE=true ;;
h) usage ;;
*) usage ;;
esac
done
# Validando parâmetros obrigatórios
if [ -z "$SOURCE" ] || [ -z "$DEST" ]; then
echo "Error: Must specify -s and -d"
usage
fi
if [ ! -d "$SOURCE" ]; then
echo "Error: Source directory '$SOURCE' does not exist"
exit 1
fi
# Executando o backup
mkdir -p "$DEST"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_NAME="backup_${TIMESTAMP}"
if [ "$COMPRESS" = true ]; then
tar -czf "${DEST}/${BACKUP_NAME}.tar.gz" "$SOURCE"
echo "✅ Compressed backup complete: ${DEST}/${BACKUP_NAME}.tar.gz"
else
cp -r "$SOURCE" "${DEST}/${BACKUP_NAME}"
echo "✅ Backup complete: ${DEST}/${BACKUP_NAME}"
fi
if [ "$VERBOSE" = true ]; then
echo "Source: $SOURCE"
echo "Destination: $DEST"
echo "Size: $(du -sh "$SOURCE" | cut -f1)"
fi
Saída:
TEXTUsage: ./backup-tool.sh -s <source_dir> -d <dest_dir> [-c] [-v] -s Source directory to back up -d Backup destination directory -c Compress backup -v Verbose output -h Show help ✅ Compressed backup complete: /backup/backup_20260707_102345.tar.gz Source: /var/www/myapp Destination: /backup Size: 12M
(10) ▶ Exemplo: Técnicas de Debugging
#!/bin/bash
# debug-demo.sh - Exemplo de debugging
# Só liga o tracing onde o debugging é necessário
echo "Script started PID: $$"
for i in 1 2 3; do
set -x # Inicia o tracing
echo "Loop iteration $i"
result=$((i * 10))
echo "Result: $result"
set +x # Para o tracing
done
echo "Script execution complete"
(11) ▶ Exemplo Abrangente: Script de Deploy de Produção
#!/bin/bash
# deploy.sh - Script de deploy de nível produção
set -euo pipefail
APP_NAME="webapp"
APP_DIR="/var/www/$APP_NAME"
BACKUP_DIR="/backup/$APP_NAME"
LOG_FILE="/var/log/deploy.log"
# Saída colorida
RED='\033[0;31m'
GREEN='\033[0;32m'
NC='\033[0m'
log() {
local level="$1"
local msg="$2"
echo -e "[$(date '+%Y-%m-%d %H:%M:%S')] [${level}] ${msg}" | tee -a "$LOG_FILE"
}
info() { log "INFO" "$1"; }
success() { log "INFO" "${GREEN}$1${NC}"; }
error() { log "ERROR" "${RED}$1${NC}"; }
cleanup() {
info "Cleaning deployment temporary files..."
}
error_handler() {
error "Deployment failed at line $1"
error "Command: $BASH_COMMAND"
cleanup
exit 1
}
usage() {
echo "Usage: $0 [options]"
echo " -b Specify Git branch (default: main)"
echo " -e Specify environment (dev/staging/prod)"
echo " -n Skip backup"
echo " -h Show help"
exit 0
}
trap error_handler ERR
trap cleanup EXIT
# --- Parsing de parâmetros ---
BRANCH="main"
ENV="prod"
SKIP_BACKUP=false
while getopts "b:e:nh" opt; do
case $opt in
b) BRANCH="$OPTARG" ;;
e) ENV="$OPTARG" ;;
n) SKIP_BACKUP=true ;;
h) usage ;;
*) usage ;;
esac
done
# --- Checagem de ambiente ---
info "Starting deployment of $APP_NAME (branch: $BRANCH, environment: $ENV)"
if [ ! -d "$APP_DIR" ]; then
error "Application directory $APP_DIR does not exist"
exit 1
fi
# --- Backup ---
if [ "$SKIP_BACKUP" = false ]; then
info "Backing up current version..."
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
mkdir -p "$BACKUP_DIR"
tar -czf "${BACKUP_DIR}/${TIMESTAMP}.tar.gz" \
--exclude=node_modules \
--exclude=.git \
"$APP_DIR"
success "Backup complete: ${BACKUP_DIR}/${TIMESTAMP}.tar.gz"
fi
# --- Deploy ---
cd "$APP_DIR"
info "Pulling latest code..."
git fetch origin
git checkout "$BRANCH"
git pull origin "$BRANCH"
info "Installing dependencies..."
npm ci --production
info "Building application..."
if [ "$ENV" = "production" ]; then
npm run build:prod
else
npm run build
fi
info "Restarting service..."
pm2 restart "$APP_NAME" || pm2 start npm --name "$APP_NAME" -- start
# --- Health check ---
info "Running health check..."
sleep 3
if curl -s -o /dev/null -w "%{http_code}" http://localhost:3000/health | grep -q "200"; then
success "✅ Deployment successful! Health check passed"
else
error "❌ Health check failed, please investigate manually"
exit 1
fi
❓ Perguntas Frequentes
P: Qual a diferença entre funções e aliases? R: Funções são bem mais poderosas que aliases: elas aceitam parâmetros, variáveis locais, valores de retorno e reuso. Aliases são apenas substituições simples de comando. Use funções para lógica complexa, aliases para atalhos simples.
P: Quando o
set -enão funciona? R: Oset -enão dispara nestes casos: 1) Em expressões de condição deif/while/until. 2) Depois de&&ou||. 3) Em pipelines (adicioneset -o pipefail). 4) Quando o comando é negado com!.
P: Quais sinais o
trapconsegue capturar? R: Os mais comuns:EXIT(script finaliza),ERR(comando falha),INT(Ctrl+C),TERM(sinal padrão dokill). Outros:HUP,QUIT,USR1,USR2.
P: Qual a diferença entre
getoptsegetopt? R: Ogetoptsé nativo do bash, mais simples, mas não aceita opções longas (como--help). Ogetopté um comando externo (do pacoteutil-linux) que aceita mistura de opções longas e curtas. Prefiragetoptspela compatibilidade.
P: Como criar arquivos temporários num script? R: Use
mktemp:tempfile=$(mktemp)cria um arquivo temporário,tempdir=$(mktemp -d)cria um diretório temporário. Arquivos temporários normalmente vão em/tmp; lembre-se de apagá-los quando não precisar mais.
📖 Resumo
- Funções
func() { local vars; return N; }encapsulam a lógica - Arrays:
${arr[@]}itera,${#arr[@]}é o tamanho,declare -Aé o array associativo set -euo pipefaildeixa os scripts robustos,trap ERR EXITcaptura erros e faz limpezagetoptsfaz o parsing dos parâmetros de linha de comandoset -x/+xé o modo debug,bash -x script.shroda o script em modo debug
📝 Exercícios
- Básico (⭐): Refatore o script de backup da Aula 20 usando funções (
log/die/check_dependencyetc.) e adicioneset -euo pipefaile tratamento de erro comtrap - Intermediário (⭐⭐): Adicione os parâmetros
-s(diretório de origem) e-d(diretório de destino) comgetopts, crie um array associativo para guardar a configuração e itere exibindo todos os itens - Desafio (⭐⭐⭐): Use
mktemppara criar um diretório temporário com limpeza automática ao final, e escreva um script de deploy de nível produção — com parsing de parâmetros, log colorido, tratamento de erros e health check