Progress indicators e logging colorido
1. Fundamentos de Logging Colorido no Terminal
Terminais modernos suportam códigos ANSI para colorir saídas de texto. Os códigos básicos são:
\e[31m → vermelho
\e[32m → verde
\e[33m → amarelo
\e[34m → azul
\e[0m → reset (volta ao padrão)
Uma abordagem robusta é criar funções helper que verificam se a saída é um terminal real (-t). Isso evita enviar códigos ANSI para pipes ou arquivos:
#!/bin/bash
info() {
if [[ -t 1 ]]; then
echo -e "\e[34m[INFO]\e[0m $*"
else
echo "[INFO] $*"
fi
}
success() {
if [[ -t 1 ]]; then
echo -e "\e[32m[OK]\e[0m $*"
else
echo "[OK] $*"
fi
}
warn() {
if [[ -t 1 ]]; then
echo -e "\e[33m[WARN]\e[0m $*" >&2
else
echo "[WARN] $*" >&2
fi
}
error() {
if [[ -t 1 ]]; then
echo -e "\e[31m[ERROR]\e[0m $*" >&2
else
echo "[ERROR] $*" >&2
fi
}
Para maior portabilidade entre terminais, use tput em vez de códigos ANSI fixos:
RED=$(tput setaf 1)
GREEN=$(tput setaf 2)
YELLOW=$(tput setaf 3)
BLUE=$(tput setaf 4)
RESET=$(tput sgr0)
2. Estruturando Mensagens de Log com Níveis
Defina níveis de log com numeração crescente de gravidade: DEBUG (0), INFO (1), WARN (2), ERROR (3), FATAL (4). Cada nível recebe timestamp e prefixo colorido:
LOG_LEVEL=1 # 0=debug, 1=info, 2=warn, 3=error, 4=fatal
log() {
local level=$1
local message=$2
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
if (( level < LOG_LEVEL )); then
return
fi
case $level in
0) [[ -t 1 ]] && echo -e "\e[90m[$timestamp] [DEBUG]\e[0m $message" || echo "[$timestamp] [DEBUG] $message" ;;
1) [[ -t 1 ]] && echo -e "\e[34m[$timestamp] [INFO]\e[0m $message" || echo "[$timestamp] [INFO] $message" ;;
2) [[ -t 1 ]] && echo -e "\e[33m[$timestamp] [WARN]\e[0m $message" >&2 || echo "[$timestamp] [WARN] $message" >&2 ;;
3) [[ -t 1 ]] && echo -e "\e[31m[$timestamp] [ERROR]\e[0m $message" >&2 || echo "[$timestamp] [ERROR] $message" >&2 ;;
4) [[ -t 1 ]] && echo -e "\e[41m\e[97m[$timestamp] [FATAL]\e[0m $message" >&2 || echo "[$timestamp] [FATAL] $message" >&2 ;;
esac
}
Redirecione corretamente: mensagens informativas vão para stdout (fd 1), enquanto avisos e erros vão para stderr (fd 2).
3. Logging para Arquivo vs. Terminal
Para duplicar a saída simultaneamente para terminal e arquivo, use tee com descritores de arquivo customizados:
exec 3>&1 # Salva stdout original
exec 4>&2 # Salva stderr original
# Redireciona tudo para tee
exec 1> >(tee -a /var/log/script.log)
exec 2> >(tee -a /var/log/script.log >&2)
# Agora toda saída vai para terminal E arquivo
echo "Esta mensagem aparece nos dois lugares"
Para logging condicional com cores apenas no terminal:
log_to_file() {
local level=$1
local message=$2
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$timestamp] [$level] $message" >> /var/log/script.log
}
log_dual() {
local level=$1
local message=$2
# No terminal (com cores)
log "$level" "$message"
# No arquivo (sem cores)
log_to_file "$level" "$message"
}
4. Progress Bar Simples com printf e \r
O caractere \r (carriage return) permite sobrescrever a linha atual. Uma barra de progresso básica:
progress_bar() {
local current=$1
local total=$2
local width=50
local percent=$(( current * 100 / total ))
local filled=$(( current * width / total ))
local empty=$(( width - filled ))
printf "\r["
printf "%0.s#" $(seq 1 $filled)
printf "%0.s-" $(seq 1 $empty)
printf "] %d%%" $percent
}
# Uso:
for i in $(seq 1 100); do
progress_bar $i 100
sleep 0.05
done
echo
Para largura dinâmica baseada no terminal:
progress_bar_dynamic() {
local current=$1
local total=$2
local width=$(( COLUMNS - 10 )) # reserva espaço para [ ] e percentual
local percent=$(( current * 100 / total ))
local filled=$(( current * width / total ))
local empty=$(( width - filled ))
printf "\r["
printf "%0.s#" $(seq 1 $filled)
printf "%0.s-" $(seq 1 $empty)
printf "] %3d%%" $percent
}
5. Spinners Animados com Caracteres Unicode
Spinners simples com caracteres ASCII e Unicode:
spinner() {
local pid=$1
local delay=0.1
local spinstr='|/-\'
local spinstr_unicode='⣾⣽⣻⢿⡿⣟⣯⣷'
# Salva posição do cursor
echo -ne "\e[s"
while ps -p $pid > /dev/null 2>&1; do
local temp=${spinstr_unicode#?}
printf "\e[u %c " "$spinstr_unicode"
local spinstr_unicode=$temp${spinstr_unicode%"$temp"}
sleep $delay
done
# Restaura cursor e limpa
printf "\e[u \e[u"
}
# Uso:
long_running_task &
spinner $!
wait
6. Indicadores Avançados com pv e dialog
O comando pv (pipe viewer) fornece progresso automático:
# Progresso durante cópia de arquivo grande
pv arquivo_grande.iso > /dev/null
# Com taxa de transferência e tempo estimado
tar czf - /dados | pv -s $(du -sb /dados | awk '{print $1}') > backup.tar.gz
Para barras de progresso em modo texto interativo, use dialog:
(
for i in $(seq 1 100); do
echo $i
sleep 0.05
done
) | dialog --gauge "Processando..." 6 50 0
7. Tratamento de Interrupções e Redesenho
Capture SIGINT para limpar a tela antes de sair:
cleanup() {
# Mostra cursor novamente
echo -ne "\e[?25h"
# Reseta cores
echo -ne "\e[0m"
# Limpa linha atual
echo -ne "\r\e[K"
exit 1
}
trap cleanup SIGINT SIGTERM
# Oculta cursor durante animações
echo -ne "\e[?25l"
# ... código com progresso ...
# Restaura cursor no final
echo -ne "\e[?25h"
8. Exemplo Prático: Script de Backup com Log e Progresso
#!/bin/bash
set -e
LOG_FILE="/var/log/backup.log"
BACKUP_DIR="/backup"
SOURCE_DIR="/dados"
# Configura cores
RED=$(tput setaf 1)
GREEN=$(tput setaf 2)
YELLOW=$(tput setaf 3)
BLUE=$(tput setaf 4)
RESET=$(tput sgr0)
# Função de log combinada
log_progress() {
local level=$1
local message=$2
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
# Log para arquivo (sem cores)
echo "[$timestamp] [$level] $message" >> "$LOG_FILE"
# Log para terminal (com cores)
case $level in
INFO) echo -e "${BLUE}[$timestamp] [INFO]${RESET} $message" ;;
OK) echo -e "${GREEN}[$timestamp] [OK]${RESET} $message" ;;
WARN) echo -e "${YELLOW}[$timestamp] [WARN]${RESET} $message" >&2 ;;
ERROR) echo -e "${RED}[$timestamp] [ERROR]${RESET} $message" >&2 ;;
esac
}
# Função de progresso
progress() {
local current=$1
local total=$2
local width=40
local percent=$(( current * 100 / total ))
local filled=$(( current * width / total ))
local empty=$(( width - filled ))
printf "\r["
printf "%0.s#" $(seq 1 $filled)
printf "%0.s-" $(seq 1 $empty)
printf "] %3d%%" $percent
}
# Tratamento de interrupção
cleanup() {
echo -e "\n${YELLOW}Backup interrompido pelo usuário${RESET}"
log_progress "WARN" "Backup interrompido"
echo -ne "\e[?25h"
exit 1
}
trap cleanup SIGINT SIGTERM
# Início do backup
log_progress "INFO" "Iniciando backup de $SOURCE_DIR para $BACKUP_DIR"
mkdir -p "$BACKUP_DIR"
# Oculta cursor
echo -ne "\e[?25l"
# Simula cópia de arquivos
total_files=50
for i in $(seq 1 $total_files); do
progress $i $total_files
sleep 0.1 # Simula processamento
done
echo
log_progress "OK" "Backup concluído com sucesso"
# Restaura cursor
echo -ne "\e[?25h"
Referências
- Bash Hackers Wiki - ANSI Escape Sequences — Referência completa sobre códigos de escape ANSI para cores e controle de terminal
- GNU Coreutils - tput manual — Documentação oficial do comando
tputpara manipulação portátil de terminais - Advanced Bash-Scripting Guide - I/O Redirection — Guia avançado sobre redirecionamento de descritores de arquivo e duplicação de saída
- pv - Pipe Viewer Manual — Documentação oficial do
pvpara monitoramento de progresso em pipes - dialog - Programmable Dialog Boxes — Manual completo do
dialogpara criação de interfaces textuais interativas - Bash Reference Manual - Trap — Documentação oficial sobre captura de sinais com
trapno Bash