BASH SCRIPTING PARA SYSADMINS Terminal $ #!/bin/bash $ set -euo pipefail $ echo "automatizar" Variaveis NAME="value" $1, $2, $@ Arrays, Strings Logica if / elif / else for / while Funcoes, Traps Scripts Monitor Disco Limpeza Logs Backup, Auditoria Cron 0 2 * * * Agendado Automatizado Do primeiro script a administracao de servidores totalmente automatizada

Se voce administra servidores Linux, Bash scripting nao e opcional — e uma habilidade fundamental. Cada tarefa repetitiva que voce realiza manualmente e candidata a automacao: verificar espaco em disco, rotacionar logs, criar backups, auditar contas de usuario, monitorar servicos. Um script Bash bem escrito executa em segundos o que levaria minutos (ou horas) de digitacao e cliques. Este guia ensina Bash scripting do zero, com foco em tarefas praticas de sysadmin que voce pode colocar em uso imediatamente.

Ao final deste artigo, voce sabera como escrever scripts que usam variaveis, condicionais, loops e funcoes. Voce entendera padroes de tratamento de erros que previnem que scripts causem danos quando algo da errado. E voce tera quatro scripts completos, prontos para producao, que resolvem problemas reais de administracao de sistemas.


Por que Bash Scripting para Sysadmins?

Bash e o shell padrao em praticamente todas as distribuicoes Linux. Vem pre-instalado no Ubuntu, Debian, CentOS, RHEL, Fedora, Arch e ate macOS (embora o macOS tenha mudado para zsh como shell interativo padrao, o Bash ainda esta disponivel). Isso significa que seus scripts rodarao em qualquer servidor sem instalar software adicional.

Veja o que o Bash scripting oferece:

  • Automacao: Transforme qualquer sequencia de comandos do terminal em um script repetivel
  • Consistencia: Scripts executam da mesma forma toda vez, eliminando erro humano
  • Velocidade: Um script pode processar milhares de arquivos ou usuarios em segundos
  • Agendamento: Combinado com cron, scripts executam automaticamente em qualquer intervalo
  • Portabilidade: Scripts Bash rodam em qualquer sistema Linux sem dependencias
  • Auditabilidade: Scripts servem como documentacao de exatamente o que foi feito e quando

Quando usar Bash vs. Python: Bash e ideal para tarefas que encadeiam ferramentas de linha de comando existentes (operacoes com arquivos, processamento de texto, gerenciamento de servicos). Se sua tarefa requer estruturas de dados complexas, chamadas de API ou processamento de JSON/YAML, considere Python.

Pre-requisitos

Antes de comecar, certifique-se de ter:

  • Um sistema Linux (Ubuntu 22.04 ou 24.04 recomendado, mas qualquer distribuicao funciona)
  • Acesso ao terminal com uma conta de usuario regular que tenha privilegios sudo
  • Um editor de texto (nano para iniciantes, vim ou VS Code para usuarios mais avancados)
  • Familiaridade basica com a linha de comando do Linux (navegar diretorios, listar arquivos, ler conteudo de arquivos)

Verifique sua versao do Bash:

bash --version

Voce deve ver a versao 4.0 ou mais recente. Ubuntu 22.04 vem com Bash 5.1 e Ubuntu 24.04 com Bash 5.2.

Seu primeiro script

Todo script Bash comeca com uma linha shebang que indica ao sistema qual interpretador usar. Crie um arquivo chamado ola.sh:

#!/bin/bash
# Meu primeiro script Bash
echo "Ola, $(whoami)! Hoje e $(date +%A), $(date +%B\ %d,\ %Y)."
echo "Voce esta executando Bash versao: $BASH_VERSION"
echo "Seu diretorio home e: $HOME"

Torne-o executavel e execute:

chmod +x ola.sh
./ola.sh

Saida:

Ola, jc! Hoje e segunda-feira, janeiro 27, 2026.
Voce esta executando Bash versao: 5.2.21(1)-release
Seu diretorio home e: /home/jc

Conceitos-chave:

  • #!/bin/bash — o shebang diz ao Linux para usar o Bash para interpretar este arquivo
  • chmod +x — define a permissao de execucao para que voce possa rodar o script diretamente
  • $(comando) — substituicao de comando: executa o comando e insere sua saida
  • $VARIAVEL — expansao de variavel: insere o valor da variavel

Boa pratica: Sempre use #!/bin/bash (nao #!/bin/sh) quando seu script usar recursos especificos do Bash como arrays, testes [[ ]] ou expansao de chaves {1..10}. Se voce precisa de portabilidade maxima entre diferentes shells, escreva scripts sh compativeis com POSIX.

Variaveis e tipos de dados

Variaveis Bash armazenam strings por padrao. Nao ha tipos separados de inteiro, ponto flutuante ou booleano — tudo e uma string que pode ser interpretada como numero em contextos aritmeticos.

Definir variaveis

#!/bin/bash

# Variaveis de string (sem espacos ao redor do =)
HOSTNAME="webserver01"
BACKUP_DIR="/var/backups"
LOG_FILE="/var/log/myscript.log"

# Aritmetica inteira usando $(( ))
MAX_RETRIES=5
CURRENT_RETRY=0
TOTAL=$((MAX_RETRIES - CURRENT_RETRY))

# Substituicao de comandos
CURRENT_DATE=$(date +%Y-%m-%d)
DISK_USAGE=$(df -h / | awk 'NR==2 {print $5}')
UPTIME=$(uptime -p)

echo "Servidor: $HOSTNAME"
echo "Diretorio de backup: $BACKUP_DIR"
echo "Data: $CURRENT_DATE"
echo "Uso de disco: $DISK_USAGE"
echo "Tempo ativo: $UPTIME"
echo "Tentativas restantes: $TOTAL"

Variaveis especiais

#!/bin/bash

echo "Nome do script: $0"
echo "Primeiro argumento: $1"
echo "Segundo argumento: $2"
echo "Todos os argumentos: $@"
echo "Numero de argumentos: $#"
echo "Status de saida do ultimo comando: $?"
echo "ID do processo deste script: $$"
echo "ID do processo do ultimo comando em background: $!"

Arrays

#!/bin/bash

# Array indexado
SERVERS=("web01" "web02" "db01" "db02" "cache01")

echo "Primeiro servidor: ${SERVERS[0]}"
echo "Todos os servidores: ${SERVERS[@]}"
echo "Numero de servidores: ${#SERVERS[@]}"

# Iterar pelo array
for server in "${SERVERS[@]}"; do
    echo "Verificando $server..."
done

# Array associativo (Bash 4+)
declare -A SERVICE_PORTS
SERVICE_PORTS[http]=80
SERVICE_PORTS[https]=443
SERVICE_PORTS[ssh]=22
SERVICE_PORTS[mysql]=3306

for service in "${!SERVICE_PORTS[@]}"; do
    echo "$service -> porta ${SERVICE_PORTS[$service]}"
done

Erro comum: Nunca coloque espacos ao redor do sinal = ao atribuir variaveis. NAME="value" esta correto. NAME = "value" falhara porque o Bash interpreta NAME como um comando.

Condicionais: if, elif, else

Condicionais controlam o fluxo do seu script com base em condicoes de teste.

Sintaxe basica

#!/bin/bash

FILE="/etc/ssh/sshd_config"

if [[ -f "$FILE" ]]; then
    echo "$FILE existe."
elif [[ -d "$FILE" ]]; then
    echo "$FILE e um diretorio, nao um arquivo."
else
    echo "$FILE nao existe."
fi

Operadores de teste de arquivo

#!/bin/bash

TARGET="/var/log/syslog"

# Testes de existencia e tipo de arquivo
[[ -e "$TARGET" ]] && echo "Existe"
[[ -f "$TARGET" ]] && echo "E um arquivo regular"
[[ -d "$TARGET" ]] && echo "E um diretorio"
[[ -L "$TARGET" ]] && echo "E um link simbolico"
[[ -r "$TARGET" ]] && echo "E legivel"
[[ -w "$TARGET" ]] && echo "E gravavel"
[[ -x "$TARGET" ]] && echo "E executavel"
[[ -s "$TARGET" ]] && echo "Tem tamanho maior que zero"

Comparacoes de strings

#!/bin/bash

ENVIRONMENT="production"

if [[ "$ENVIRONMENT" == "production" ]]; then
    echo "Executando em modo producao -- cautela extra!"
    VERBOSE=false
elif [[ "$ENVIRONMENT" == "staging" ]]; then
    echo "Executando em modo staging."
    VERBOSE=true
else
    echo "Ambiente desconhecido: $ENVIRONMENT"
    exit 1
fi

# Verificacoes de string
[[ -z "$VAR" ]] && echo "VAR esta vazia ou nao definida"
[[ -n "$VAR" ]] && echo "VAR nao esta vazia"

# Correspondencia de padroes com [[ ]]
if [[ "$HOSTNAME" == web* ]]; then
    echo "Este e um servidor web."
fi

Comparacoes numericas

#!/bin/bash

DISK_PERCENT=$(df / | awk 'NR==2 {print $5}' | tr -d '%')

if [[ "$DISK_PERCENT" -gt 90 ]]; then
    echo "CRITICO: Uso de disco em ${DISK_PERCENT}%"
elif [[ "$DISK_PERCENT" -gt 75 ]]; then
    echo "AVISO: Uso de disco em ${DISK_PERCENT}%"
else
    echo "OK: Uso de disco em ${DISK_PERCENT}%"
fi

Dica: Use [[ ]] (colchetes duplos) ao inves de [ ] (colchetes simples). Colchetes duplos sao uma extensao Bash que lida melhor com espacos em variaveis, suporta correspondencia de padroes com == e permite &&/|| dentro da expressao de teste.

Loops: for, while, until

Loops permitem repetir operacoes em arquivos, servidores, usuarios ou qualquer lista de itens.

Loop for

#!/bin/bash

# Iterar sobre uma lista
for PACKAGE in nginx curl wget htop; do
    echo "Instalando $PACKAGE..."
    sudo apt install -y "$PACKAGE" > /dev/null 2>&1
    echo "$PACKAGE instalado."
done

# Iterar sobre um intervalo
for i in {1..10}; do
    echo "Iteracao $i"
done

# Loop for estilo C
for ((i = 0; i < 5; i++)); do
    echo "Contador: $i"
done

# Iterar sobre arquivos
for FILE in /var/log/*.log; do
    SIZE=$(stat -c%s "$FILE" 2>/dev/null || echo 0)
    echo "$FILE: $SIZE bytes"
done

Loop while

#!/bin/bash

# Ler um arquivo linha por linha
while IFS= read -r line; do
    echo "Processando: $line"
done < /etc/hosts

# Loop baseado em contador
COUNTER=0
MAX=5
while [[ $COUNTER -lt $MAX ]]; do
    echo "Tentativa $((COUNTER + 1)) de $MAX"
    COUNTER=$((COUNTER + 1))
done

# Esperar que um servico fique disponivel
RETRIES=0
MAX_RETRIES=30
while ! curl -s http://localhost:8080/health > /dev/null 2>&1; do
    RETRIES=$((RETRIES + 1))
    if [[ $RETRIES -ge $MAX_RETRIES ]]; then
        echo "O servico nao iniciou em $MAX_RETRIES tentativas."
        exit 1
    fi
    echo "Aguardando servico... (tentativa $RETRIES/$MAX_RETRIES)"
    sleep 2
done
echo "O servico esta pronto!"

Loop until

#!/bin/bash

# Loop until -- executa ate que a condicao se torne verdadeira
COUNT=0
until [[ $COUNT -ge 5 ]]; do
    echo "Contagem e $COUNT"
    COUNT=$((COUNT + 1))
done

Funcoes

Funcoes permitem organizar seus scripts em blocos reutilizaveis. Isso e critico para scripts maiores que realizam multiplas tarefas relacionadas.

#!/bin/bash

# Definicao de funcao
log_message() {
    local LEVEL="$1"
    local MESSAGE="$2"
    local TIMESTAMP
    TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
    echo "[$TIMESTAMP] [$LEVEL] $MESSAGE"
}

# Funcao com valor de retorno
check_service() {
    local SERVICE_NAME="$1"
    if systemctl is-active --quiet "$SERVICE_NAME"; then
        return 0  # sucesso
    else
        return 1  # falha
    fi
}

# Funcao que produz um valor (capturar com substituicao de comando)
get_memory_usage() {
    free -m | awk 'NR==2 {printf "%.1f", $3/$2 * 100}'
}

# Usando as funcoes
log_message "INFO" "Iniciando verificacao do sistema..."

SERVICES=("nginx" "ssh" "cron")
for svc in "${SERVICES[@]}"; do
    if check_service "$svc"; then
        log_message "INFO" "$svc esta em execucao."
    else
        log_message "WARN" "$svc NAO esta em execucao!"
    fi
done

MEM_USAGE=$(get_memory_usage)
log_message "INFO" "Uso de memoria: ${MEM_USAGE}%"

Pontos-chave sobre funcoes Bash:

  • Use local para declarar variaveis com escopo de funcao (previne poluir o escopo global)
  • Funcoes usam return para codigos de saida (0-255), nao para retornar dados
  • Para retornar dados, use echo dentro da funcao e capture com $(nome_funcao)
  • Argumentos sao acessados com $1, $2, $@ dentro da funcao

Entrada/Saida e redirecionamento

Entender o redirecionamento de E/S e essencial para escrever scripts que registram sua saida, processam arquivos e tratam erros corretamente.

#!/bin/bash

# Redirecionar stdout para um arquivo (sobrescrever)
echo "Entrada de log" > /tmp/output.log

# Redirecionar stdout para um arquivo (adicionar)
echo "Outra entrada" >> /tmp/output.log

# Redirecionar stderr para um arquivo
command_that_fails 2> /tmp/error.log

# Redirecionar tanto stdout quanto stderr para o mesmo arquivo
some_command > /tmp/all_output.log 2>&1

# Sintaxe moderna (Bash 4+) para redirecionar ambos
some_command &> /tmp/all_output.log

# Descartar saida completamente
noisy_command > /dev/null 2>&1

# Here document (heredoc) para entrada multilinhas
cat << 'EOF' > /tmp/config.txt
server {
    listen 80;
    server_name example.com;
    root /var/www/html;
}
EOF

# Encadear saida para outro comando
ps aux | grep nginx | grep -v grep

# Tee: escrever em arquivo E stdout simultaneamente
echo "Verificacao do sistema aprovada" | tee -a /var/log/checks.log

# Substituicao de processo
diff <(ls /dir1) <(ls /dir2)

Lendo entrada do usuario

#!/bin/bash

read -p "Digite o nome do servidor: " SERVER_NAME
read -sp "Digite a senha: " PASSWORD
echo ""  # nova linha apos entrada oculta

read -p "Prosseguir com o deploy em $SERVER_NAME? (s/n): " CONFIRM
if [[ "$CONFIRM" != "s" ]]; then
    echo "Deploy cancelado."
    exit 0
fi

Tratamento de erros: set -euo pipefail e trap

O tratamento adequado de erros separa scripts de qualidade de producao dos frageis. Sem tratamento de erros, um script pode falhar silenciosamente no meio da execucao, deixando seu sistema em um estado inconsistente.

O cabecalho de modo estrito

#!/bin/bash
set -euo pipefail
IFS=$'\n\t'

O que cada opcao faz:

  • set -e — Sai imediatamente se qualquer comando retornar um status de saida diferente de zero
  • set -u — Trata referencias a variaveis nao definidas como erros
  • set -o pipefail — Se qualquer comando em um pipeline falhar, todo o pipeline falha (nao apenas o ultimo comando)
  • IFS=$'\n\t' — Define o Separador de Campo Interno para nova linha e tabulacao apenas (previne divisao de palavras em espacos em nomes de arquivo)

Usando trap para limpeza

#!/bin/bash
set -euo pipefail

TEMP_DIR=""

cleanup() {
    local EXIT_CODE=$?
    if [[ -n "$TEMP_DIR" && -d "$TEMP_DIR" ]]; then
        rm -rf "$TEMP_DIR"
        echo "Diretorio temporario limpo: $TEMP_DIR"
    fi
    if [[ $EXIT_CODE -ne 0 ]]; then
        echo "Script falhou com codigo de saida: $EXIT_CODE"
    fi
    exit $EXIT_CODE
}

trap cleanup EXIT ERR

TEMP_DIR=$(mktemp -d)
echo "Trabalhando em $TEMP_DIR"

# Sua logica de script aqui...
# Se algo falhar, cleanup() executa automaticamente
cp /some/important/file "$TEMP_DIR/"
process_data "$TEMP_DIR"

Tratando falhas esperadas

#!/bin/bash
set -euo pipefail

# Metodo 1: Use || true para permitir que um comando falhe
grep "pattern" /var/log/syslog || true

# Metodo 2: Use if para verificar o resultado
if grep -q "error" /var/log/syslog; then
    echo "Erros encontrados no syslog"
else
    echo "Nenhum erro encontrado"
fi

# Metodo 3: Capturar e verificar codigo de saida
set +e  # desabilitar temporariamente saida-em-erro
risky_command
EXIT_CODE=$?
set -e  # reabilitar

if [[ $EXIT_CODE -ne 0 ]]; then
    echo "Comando falhou com codigo $EXIT_CODE"
fi

Trabalhando com arquivos e diretorios

Scripts de sysadmin frequentemente precisam criar, verificar, mover e processar arquivos. Aqui estao os padroes mais comuns:

#!/bin/bash
set -euo pipefail

# Criar uma estrutura de diretorios
BACKUP_BASE="/var/backups/myapp"
BACKUP_DIR="${BACKUP_BASE}/$(date +%Y-%m-%d)"
mkdir -p "$BACKUP_DIR"

# Verificar se um arquivo existe antes de operar nele
CONFIG_FILE="/etc/myapp/config.yml"
if [[ ! -f "$CONFIG_FILE" ]]; then
    echo "ERRO: Arquivo de configuracao nao encontrado: $CONFIG_FILE"
    exit 1
fi

# Encontrar arquivos com mais de 30 dias
find /var/log/myapp -name "*.log" -mtime +30 -type f -print

# Encontrar e deletar arquivos antigos (com confirmacao)
find /tmp -name "*.tmp" -mtime +7 -type f -delete

# Obter tamanho do arquivo em bytes
FILE_SIZE=$(stat -c%s "$CONFIG_FILE")
echo "Tamanho do arquivo de configuracao: $FILE_SIZE bytes"

# Contar linhas em um arquivo
LINE_COUNT=$(wc -l < "$CONFIG_FILE")
echo "Configuracao tem $LINE_COUNT linhas"

# Ler um arquivo de configuracao, pulando comentarios e linhas vazias
while IFS='=' read -r key value; do
    # Pular comentarios e linhas vazias
    [[ "$key" =~ ^#.*$ || -z "$key" ]] && continue
    echo "Config: $key = $value"
done < "$CONFIG_FILE"

# Criacao segura de arquivo temporario
TEMP_FILE=$(mktemp /tmp/myapp.XXXXXX)
echo "Usando arquivo temporario: $TEMP_FILE"
# Sempre limpe arquivos temporarios (use trap como mostrado anteriormente)

Scripts praticos para sysadmins

Aqui estao quatro scripts completos, prontos para producao, que resolvem problemas comuns de administracao de sistemas.

Script 1: Monitor de espaco em disco com alerta por e-mail

#!/bin/bash
set -euo pipefail

# Configuracao
THRESHOLD=80
ALERT_EMAIL="admin@example.com"
LOG_FILE="/var/log/disk-monitor.log"

log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}

log "Iniciando verificacao de espaco em disco..."

ALERT_TRIGGERED=false

while IFS= read -r line; do
    # Pular a linha de cabecalho
    [[ "$line" == Filesystem* ]] && continue

    USAGE=$(echo "$line" | awk '{print $5}' | tr -d '%')
    MOUNT=$(echo "$line" | awk '{print $6}')
    FILESYSTEM=$(echo "$line" | awk '{print $1}')

    if [[ "$USAGE" -ge "$THRESHOLD" ]]; then
        ALERT_TRIGGERED=true
        log "AVISO: $MOUNT esta em ${USAGE}% ($FILESYSTEM)"
    else
        log "OK: $MOUNT esta em ${USAGE}%"
    fi
done < <(df -h --output=source,size,used,avail,pcent,target -x tmpfs -x devtmpfs)

if [[ "$ALERT_TRIGGERED" == true ]]; then
    SUBJECT="[ALERTA] Aviso de espaco em disco em $(hostname)"
    BODY="Um ou mais sistemas de arquivos excederam ${THRESHOLD}% de uso em $(hostname) em $(date).\n\n$(df -h)"
    echo -e "$BODY" | mail -s "$SUBJECT" "$ALERT_EMAIL" 2>/dev/null || \
        log "AVISO: Nao foi possivel enviar alerta por e-mail"
fi

log "Verificacao de espaco em disco concluida."

Script 2: Limpeza e rotacao de logs

#!/bin/bash
set -euo pipefail

# Configuracao
LOG_DIRS=("/var/log/myapp" "/var/log/nginx" "/home/*/logs")
MAX_AGE_DAYS=30
COMPRESS_AGE_DAYS=7
DRY_RUN=false

log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1"
}

# Analisar argumentos
while [[ $# -gt 0 ]]; do
    case "$1" in
        --dry-run) DRY_RUN=true; shift ;;
        --max-age) MAX_AGE_DAYS="$2"; shift 2 ;;
        *) echo "Opcao desconhecida: $1"; exit 1 ;;
    esac
done

TOTAL_FREED=0

for DIR_PATTERN in "${LOG_DIRS[@]}"; do
    for DIR in $DIR_PATTERN; do
        [[ -d "$DIR" ]] || continue
        log "Processando $DIR..."

        # Comprimir logs com mais de COMPRESS_AGE_DAYS dias
        while IFS= read -r -d '' file; do
            if [[ "$DRY_RUN" == true ]]; then
                log "  [SIMULACAO] Comprimiria: $file"
            else
                gzip "$file"
                log "  Comprimido: $file"
            fi
        done < <(find "$DIR" -name "*.log" -mtime +"$COMPRESS_AGE_DAYS" -type f -print0)

        # Deletar logs comprimidos com mais de MAX_AGE_DAYS dias
        while IFS= read -r -d '' file; do
            SIZE=$(stat -c%s "$file")
            TOTAL_FREED=$((TOTAL_FREED + SIZE))
            if [[ "$DRY_RUN" == true ]]; then
                log "  [SIMULACAO] Deletaria: $file ($SIZE bytes)"
            else
                rm -f "$file"
                log "  Deletado: $file ($SIZE bytes)"
            fi
        done < <(find "$DIR" -name "*.gz" -mtime +"$MAX_AGE_DAYS" -type f -print0)
    done
done

FREED_MB=$((TOTAL_FREED / 1024 / 1024))
log "Espaco total liberado: ${FREED_MB} MB"

Script 3: Script de backup automatizado

#!/bin/bash
set -euo pipefail

# Configuracao
BACKUP_SOURCE="/var/www /etc/nginx /etc/myapp"
BACKUP_DEST="/var/backups"
RETENTION_DAYS=14
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
HOSTNAME=$(hostname)
BACKUP_FILE="${BACKUP_DEST}/${HOSTNAME}_backup_${TIMESTAMP}.tar.gz"
LOG_FILE="/var/log/backup.log"

log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}

cleanup() {
    local EXIT_CODE=$?
    if [[ $EXIT_CODE -ne 0 ]]; then
        log "ERRO: Backup falhou com codigo de saida $EXIT_CODE"
        rm -f "$BACKUP_FILE"
    fi
}
trap cleanup EXIT

# Garantir que o diretorio de backup existe
mkdir -p "$BACKUP_DEST"

log "Iniciando backup de: $BACKUP_SOURCE"
log "Destino: $BACKUP_FILE"

# Criar tarball comprimido
# shellcheck disable=SC2086
tar -czf "$BACKUP_FILE" $BACKUP_SOURCE 2>/dev/null

BACKUP_SIZE=$(stat -c%s "$BACKUP_FILE")
BACKUP_SIZE_MB=$((BACKUP_SIZE / 1024 / 1024))
log "Backup criado: ${BACKUP_SIZE_MB} MB"

# Verificar o backup
if tar -tzf "$BACKUP_FILE" > /dev/null 2>&1; then
    log "Verificacao de integridade do backup: APROVADA"
else
    log "ERRO: Verificacao de integridade do backup FALHOU"
    exit 1
fi

# Remover backups antigos
DELETED_COUNT=0
while IFS= read -r -d '' old_backup; do
    rm -f "$old_backup"
    DELETED_COUNT=$((DELETED_COUNT + 1))
    log "Backup antigo removido: $old_backup"
done < <(find "$BACKUP_DEST" -name "${HOSTNAME}_backup_*.tar.gz" -mtime +"$RETENTION_DAYS" -type f -print0)

log "Backup concluido. Removidos $DELETED_COUNT backup(s) antigo(s)."

Script 4: Auditoria de contas de usuario

#!/bin/bash
set -euo pipefail

# Configuracao
OUTPUT_FILE="/tmp/user_audit_$(date +%Y%m%d).txt"
MIN_UID=1000

log() {
    echo "$1" | tee -a "$OUTPUT_FILE"
}

# Limpar saida anterior
> "$OUTPUT_FILE"

log "============================================"
log "  RELATORIO DE AUDITORIA DE CONTAS DE USUARIO"
log "  Host: $(hostname)"
log "  Data: $(date '+%Y-%m-%d %H:%M:%S')"
log "============================================"
log ""

# Listar todos os usuarios humanos (UID >= 1000)
log "--- Contas de usuarios humanos (UID >= $MIN_UID) ---"
while IFS=: read -r username _ uid gid _ home shell; do
    if [[ $uid -ge $MIN_UID ]]; then
        LAST_LOGIN=$(lastlog -u "$username" 2>/dev/null | tail -1 | awk '{print $4, $5, $6, $7, $8, $9}')
        GROUPS=$(groups "$username" 2>/dev/null | cut -d: -f2)
        HAS_SUDO="Nao"
        if groups "$username" 2>/dev/null | grep -qw "sudo\|wheel"; then
            HAS_SUDO="Sim"
        fi
        log "  Usuario: $username (UID: $uid)"
        log "    Home: $home"
        log "    Shell: $shell"
        log "    Sudo: $HAS_SUDO"
        log "    Grupos:$GROUPS"
        log "    Ultimo login: $LAST_LOGIN"
        log ""
    fi
done < /etc/passwd

# Verificar usuarios com senhas vazias
log "--- Usuarios com senhas vazias ---"
EMPTY_PASS=$(sudo awk -F: '($2 == "") {print $1}' /etc/shadow 2>/dev/null || true)
if [[ -n "$EMPTY_PASS" ]]; then
    log "  AVISO: $EMPTY_PASS"
else
    log "  Nenhum encontrado (bom)."
fi
log ""

# Verificar usuarios com UID 0 (equivalentes ao root)
log "--- Usuarios com UID 0 (Privilegios root) ---"
while IFS=: read -r username _ uid _; do
    if [[ $uid -eq 0 ]]; then
        log "  $username (UID: 0)"
    fi
done < /etc/passwd
log ""

# Listar chaves SSH autorizadas
log "--- Chaves SSH autorizadas ---"
for HOME_DIR in /home/*; do
    USERNAME=$(basename "$HOME_DIR")
    AUTH_KEYS="$HOME_DIR/.ssh/authorized_keys"
    if [[ -f "$AUTH_KEYS" ]]; then
        KEY_COUNT=$(wc -l < "$AUTH_KEYS")
        log "  $USERNAME: $KEY_COUNT chave(s) em $AUTH_KEYS"
    fi
done
log ""

log "Relatorio salvo em: $OUTPUT_FILE"

Agendamento com cron

Cron e o agendador de tarefas padrao do Linux. Executa scripts em intervalos definidos sem intervencao manual.

Editando o crontab

# Editar crontab para o usuario atual
crontab -e

# Editar crontab para um usuario especifico (requer root)
sudo crontab -u www-data -e

# Listar entradas atuais do crontab
crontab -l

Sintaxe do cron

# ┌───────────── minuto (0 - 59)
# │ ┌───────────── hora (0 - 23)
# │ │ ┌───────────── dia do mes (1 - 31)
# │ │ │ ┌───────────── mes (1 - 12)
# │ │ │ │ ┌───────────── dia da semana (0 - 7, 0 e 7 = Domingo)
# │ │ │ │ │
# * * * * * comando

Agendamentos comuns de cron

# Executar monitor de disco a cada hora
0 * * * * /usr/local/bin/disk-monitor.sh >> /var/log/disk-monitor.log 2>&1

# Executar backup diariamente as 2h
0 2 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1

# Executar limpeza de logs todo domingo as 3h
0 3 * * 0 /usr/local/bin/log-cleanup.sh >> /var/log/cleanup.log 2>&1

# Executar auditoria de usuarios no 1o de cada mes
0 9 1 * * /usr/local/bin/user-audit.sh >> /var/log/user-audit.log 2>&1

# Executar verificacao de saude a cada 5 minutos
*/5 * * * * /usr/local/bin/health-check.sh > /dev/null 2>&1

Regras importantes do cron: Sempre use caminhos absolutos em jobs cron (nao caminhos relativos). O cron executa com um ambiente minimo, entao PATH pode nao incluir /usr/local/bin. Sempre redirecione a saida para um arquivo de log ou /dev/null para prevenir que o cron envie e-mail a cada execucao.

Usando variaveis de ambiente no cron

# Definir variaveis de ambiente no topo do seu crontab
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
MAILTO=admin@example.com

# Agora seus jobs tem acesso a caminhos comuns
0 2 * * * /usr/local/bin/backup.sh

Tabela de referencia de operadores Bash

CategoriaOperadorDescricaoExemplo
Testes de arquivo-fArquivo existe e e regular[[ -f /etc/hosts ]]
-dDiretorio existe[[ -d /var/log ]]
-eArquivo ou diretorio existe[[ -e /tmp/lock ]]
-rArquivo e legivel[[ -r config.yml ]]
-wArquivo e gravavel[[ -w /var/log/app.log ]]
-xArquivo e executavel[[ -x script.sh ]]
-sTamanho maior que zero[[ -s output.log ]]
-LE um link simbolico[[ -L /usr/bin/python ]]
Strings==Strings sao iguais[[ "$a" == "$b" ]]
!=Strings nao sao iguais[[ "$a" != "$b" ]]
-zString esta vazia[[ -z "$var" ]]
-nString nao esta vazia[[ -n "$var" ]]
=~Correspondencia regex[[ "$s" =~ ^[0-9]+$ ]]
Numerico-eqIgual[[ $a -eq $b ]]
-neNao igual[[ $a -ne $b ]]
-gtMaior que[[ $a -gt $b ]]
-geMaior ou igual[[ $a -ge $b ]]
-ltMenor que[[ $a -lt $b ]]
-leMenor ou igual[[ $a -le $b ]]
Logico&&AND (dentro de [[ ]])[[ $a -gt 0 && $a -lt 100 ]]
||OR (dentro de [[ ]])[[ $a -eq 0 || $a -eq 1 ]]
!NOT[[ ! -f /tmp/lock ]]

Solucao de problemas e depuracao

Quando um script nao funciona como esperado, use estas tecnicas para encontrar o problema.

Depurar com set -x

#!/bin/bash
set -x  # Imprimir cada comando antes de executa-lo

NAME="world"
echo "Hello, $NAME"
# Saida:
# + NAME=world
# + echo 'Hello, world'
# Hello, world

Voce pode habilitar a depuracao para secoes especificas:

#!/bin/bash

echo "Saida normal aqui"

set -x  # Iniciar saida de depuracao
RESULT=$(some_complex_command)
process "$RESULT"
set +x  # Parar saida de depuracao

echo "De volta a saida normal"

Executar um script em modo de depuracao

# Depurar o script inteiro sem modifica-lo
bash -x ./myscript.sh

# Modo verboso (imprimir linhas ao serem lidas)
bash -v ./myscript.sh

# Verboso + rastreamento combinados
bash -xv ./myscript.sh

ShellCheck: Analise estatica

ShellCheck e uma ferramenta essencial que detecta erros comuns de Bash, erros de citacao e problemas de portabilidade.

# Instalar ShellCheck
sudo apt install -y shellcheck

# Analisar um script
shellcheck myscript.sh

# Saida de exemplo:
# In myscript.sh line 5:
# echo $VARIABLE
#      ^--------^ SC2086: Double quote to prevent globbing and word splitting.

Lista de verificacao de depuracao comum

  1. Verificar o shebang: E #!/bin/bash (nao #!/bin/sh)?
  2. Verificar permissoes: Voce executou chmod +x script.sh?
  3. Verificar finais de linha: Finais de linha do Windows (\r\n) causam /bin/bash^M: bad interpreter. Corrija com dos2unix script.sh.
  4. Colocar aspas nas variaveis: Sempre use "$VARIABLE" e nao $VARIABLE para lidar com espacos e caracteres especiais.
  5. Verificar codigos de saida: Apos cada comando critico, verifique $? ou use set -e.
  6. Testar com valores fixos: Substitua variaveis por valores conhecidos para isolar o problema.
  7. Verificar ambiente do cron: Se um script funciona manualmente mas nao no cron, o problema geralmente e PATH ou variaveis de ambiente ausentes.
# Script rapido para despejar o ambiente do cron
* * * * * env > /tmp/cron-env.txt 2>&1
# (remover apos verificar)

Resumo

Bash scripting e uma das habilidades mais valiosas que um administrador de sistemas Linux pode desenvolver. Neste guia, voce aprendeu como:

  • Escrever scripts com estrutura adequada (shebang, permissoes, modo estrito)
  • Usar variaveis, arrays e substituicao de comandos
  • Controlar o fluxo com condicionais e loops
  • Organizar codigo com funcoes
  • Tratar erros com seguranca usando set -euo pipefail e trap
  • Construir scripts praticos para monitoramento de disco, limpeza de logs, backups e auditoria de usuarios
  • Agendar scripts com cron para execucao totalmente automatizada
  • Depurar scripts com set -x e ShellCheck

Os quatro scripts praticos deste artigo sao pontos de partida. Personalize-os para seu ambiente especifico, adicione notificacoes por e-mail, integre-os com sistemas de monitoramento e construa sobre eles conforme suas necessidades cresam.

Para topicos relacionados de administracao de servidores, confira nosso Checklist de seguranca para servidores Linux: 20 passos essenciais para proteger os servidores onde seus scripts rodam, e Hardening SSH: 12 passos para proteger seu servidor Linux para blindar o acesso SSH que voce usa para gerencia-los.