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

1. O Que Você Vai Aprender


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:

BASH
#!/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."

TEXT
#!/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

TEXT
# 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

TEXT
# 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

TEXT
# 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

TEXT
# 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

TEXT
# 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

TEXT
#!/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

TEXT
#!/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

BASH
#!/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:

TEXT
Starting execution...
Cleaning temporary files...
Done

(9) ▶ Exemplo: Parsing de Parâmetros com getopts

BASH
#!/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:

TEXT
Usage: ./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

TEXT
#!/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

BASH
#!/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 -e não funciona? R: O set -e não dispara nestes casos: 1) Em expressões de condição de if/while/until. 2) Depois de && ou ||. 3) Em pipelines (adicione set -o pipefail). 4) Quando o comando é negado com !.

P: Quais sinais o trap consegue capturar? R: Os mais comuns: EXIT (script finaliza), ERR (comando falha), INT (Ctrl+C), TERM (sinal padrão do kill). Outros: HUP, QUIT, USR1, USR2.

P: Qual a diferença entre getopts e getopt? R: O getopts é nativo do bash, mais simples, mas não aceita opções longas (como --help). O getopt é um comando externo (do pacote util-linux) que aceita mistura de opções longas e curtas. Prefira getopts pela 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


📝 Exercícios

  1. Básico (⭐): Refatore o script de backup da Aula 20 usando funções (log/die/check_dependency etc.) e adicione set -euo pipefail e tratamento de erro com trap
  2. Intermediário (⭐⭐): Adicione os parâmetros -s (diretório de origem) e -d (diretório de destino) com getopts, crie um array associativo para guardar a configuração e itere exibindo todos os itens
  3. Desafio (⭐⭐⭐): Use mktemp para 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
Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%