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 (
nanopara iniciantes,vimou 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 arquivochmod +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 scriptsshcompativeis 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 interpretaNAMEcomo 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
localpara declarar variaveis com escopo de funcao (previne poluir o escopo global) - Funcoes usam
returnpara codigos de saida (0-255), nao para retornar dados - Para retornar dados, use
echodentro 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 zeroset -u— Trata referencias a variaveis nao definidas como errosset -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
PATHpode nao incluir/usr/local/bin. Sempre redirecione a saida para um arquivo de log ou/dev/nullpara 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
| Categoria | Operador | Descricao | Exemplo |
|---|---|---|---|
| Testes de arquivo | -f | Arquivo existe e e regular | [[ -f /etc/hosts ]] |
-d | Diretorio existe | [[ -d /var/log ]] | |
-e | Arquivo ou diretorio existe | [[ -e /tmp/lock ]] | |
-r | Arquivo e legivel | [[ -r config.yml ]] | |
-w | Arquivo e gravavel | [[ -w /var/log/app.log ]] | |
-x | Arquivo e executavel | [[ -x script.sh ]] | |
-s | Tamanho maior que zero | [[ -s output.log ]] | |
-L | E um link simbolico | [[ -L /usr/bin/python ]] | |
| Strings | == | Strings sao iguais | [[ "$a" == "$b" ]] |
!= | Strings nao sao iguais | [[ "$a" != "$b" ]] | |
-z | String esta vazia | [[ -z "$var" ]] | |
-n | String nao esta vazia | [[ -n "$var" ]] | |
=~ | Correspondencia regex | [[ "$s" =~ ^[0-9]+$ ]] | |
| Numerico | -eq | Igual | [[ $a -eq $b ]] |
-ne | Nao igual | [[ $a -ne $b ]] | |
-gt | Maior que | [[ $a -gt $b ]] | |
-ge | Maior ou igual | [[ $a -ge $b ]] | |
-lt | Menor que | [[ $a -lt $b ]] | |
-le | Menor 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
- Verificar o shebang: E
#!/bin/bash(nao#!/bin/sh)? - Verificar permissoes: Voce executou
chmod +x script.sh? - Verificar finais de linha: Finais de linha do Windows (
\r\n) causam/bin/bash^M: bad interpreter. Corrija comdos2unix script.sh. - Colocar aspas nas variaveis: Sempre use
"$VARIABLE"e nao$VARIABLEpara lidar com espacos e caracteres especiais. - Verificar codigos de saida: Apos cada comando critico, verifique
$?ou useset -e. - Testar com valores fixos: Substitua variaveis por valores conhecidos para isolar o problema.
- Verificar ambiente do cron: Se um script funciona manualmente mas nao no cron, o problema geralmente e
PATHou 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 pipefailetrap - 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 -xe 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.