Interactive scripts: read, select e menus
1. Fundamentos do comando read
O comando read é a porta de entrada para scripts interativos em Bash. Sua sintaxe básica é simples, mas as opções disponíveis oferecem grande flexibilidade:
read -p "Digite seu nome: " nome
echo "Olá, $nome!"
Opções essenciais:
-p "prompt"— Exibe um prompt antes da leitura-s— Modo silencioso (útil para senhas)-t N— Timeout de N segundos-n N— Lê apenas N caracteres
# Exemplo com múltiplas opções
read -s -p "Senha: " senha
echo
read -t 5 -p "Nome (5 segundos): " nome || nome="Convidado"
Lendo múltiplas variáveis com IFS:
# Lendo dados separados por dois-pontos
IFS=':' read -p "Digite hora:minuto:segundo: " h m s
echo "São $h horas, $m minutos e $s segundos"
Tratamento de entrada vazia:
read -p "Nome do projeto [projeto_padrao]: " projeto
projeto=${projeto:-projeto_padrao}
echo "Nome definido: $projeto"
2. Validação e sanitização de entrada
Validar entradas é crucial para scripts robustos. Usamos expressões regulares com [[ ]] e loops while:
# Validando número inteiro
while true; do
read -p "Digite um número inteiro: " numero
if [[ "$numero" =~ ^-?[0-9]+$ ]]; then
echo "Número válido: $numero"
break
else
echo "Erro: digite apenas números inteiros."
fi
done
Verificando múltiplos tipos:
# Validação de email simples
while true; do
read -p "Email: " email
if [[ "$email" =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then
echo "Email válido!"
break
fi
echo "Formato inválido. Tente novamente."
done
Sempre use read -r para preservar barras invertidas e caracteres especiais:
read -r -p "Caminho do arquivo (use barras): " caminho
echo "Processando: $caminho"
3. Construindo menus com select
O comando select cria menus interativos automaticamente:
echo "Escolha uma opção:"
select opcao in "Listar" "Criar" "Deletar" "Sair"; do
case $REPLY in
1) echo "Listando arquivos..." ;;
2) echo "Criando arquivo..." ;;
3) echo "Deletando arquivo..." ;;
4) echo "Saindo..."; break ;;
*) echo "Opção inválida" ;;
esac
done
Personalizando o prompt:
PS3="Digite sua escolha (1-4): "
select opcao in "Opção A" "Opção B" "Opção C" "Sair"; do
# $REPLY contém o número digitado
# $opcao contém o texto da opção
echo "Você escolheu: $opcao (índice $REPLY)"
[[ "$opcao" == "Sair" ]] && break
done
4. Menus dinâmicos e arrays associativos
Populando menus a partir de arrays:
opcoes=("Criar usuário" "Listar usuários" "Deletar usuário" "Sair")
PS3="Selecione: "
select acao in "${opcoes[@]}"; do
case $REPLY in
1) echo "Criando..." ;;
2) echo "Listando..." ;;
3) echo "Deletando..." ;;
4) break ;;
esac
done
Usando arrays associativos para mapear descrições a ações:
declare -A acoes
acoes["1: Criar"]="criar_usuario"
acoes["2: Listar"]="listar_usuarios"
acoes["3: Sair"]="sair"
select escolha in "${!acoes[@]}"; do
funcao="${acoes[$escolha]}"
if [[ -n "$funcao" ]]; then
$funcao
else
echo "Opção inválida"
fi
done
Menu de seleção de arquivos:
select arquivo in *; do
if [[ -n "$arquivo" && -f "$arquivo" ]]; then
echo "Arquivo selecionado: $arquivo"
wc -l "$arquivo"
break
fi
echo "Selecione um arquivo válido"
done
5. Navegação avançada com case e submenus
Combinando select com case para ações condicionais:
menu_principal() {
PS3="Menu principal > "
select opcao in "Gerenciar usuários" "Gerenciar arquivos" "Sair"; do
case $REPLY in
1) menu_usuarios ;;
2) menu_arquivos ;;
3) echo "Saindo..."; exit 0 ;;
*) echo "Opção inválida" ;;
esac
done
}
menu_usuarios() {
PS3="Usuários > "
select opcao in "Criar" "Listar" "Voltar"; do
case $REPLY in
1) echo "Criando usuário..." ;;
2) echo "Listando usuários..." ;;
3) return ;;
*) echo "Opção inválida" ;;
esac
done
}
Gerenciamento de estado entre menus:
declare -g usuario_atual=""
declare -g diretorio_atual="$PWD"
menu_arquivos() {
PS3="Arquivos em $diretorio_atual > "
select arquivo in * "Voltar"; do
if [[ "$arquivo" == "Voltar" ]]; then
return
elif [[ -f "$arquivo" ]]; then
echo "Processando $arquivo..."
fi
done
}
6. Tratamento de interrupções e sinais
Capturando Ctrl+C para saída limpa:
cleanup() {
echo -e "\n\nOperação interrompida pelo usuário."
stty sane
exit 1
}
trap cleanup SIGINT SIGTERM
# Menu com timeout
while true; do
if ! read -t 10 -p "Ação (ou timeout em 10s): " acao; then
echo -e "\nTimeout atingido. Executando ação padrão..."
acao="default"
fi
case $acao in
"exit") break ;;
"default") echo "Ação padrão executada" ;;
*) echo "Comando: $acao" ;;
esac
done
7. Integração com logging colorido e progresso
Adicionando cores a prompts e mensagens:
# Cores
VERDE='\033[0;32m'
VERMELHO='\033[0;31m'
AMARELO='\033[1;33m'
RESET='\033[0m'
log_info() { echo -e "${VERDE}[INFO]${RESET} $1"; }
log_erro() { echo -e "${VERMELHO}[ERRO]${RESET} $1" >&2; }
log_alerta() { echo -e "${AMARELO}[ALERTA]${RESET} $1"; }
# Barra de progresso simples
progresso() {
local total=$1
local atual=$2
local percent=$(( atual * 100 / total ))
local barras=$(( percent / 2 ))
printf "\r[%-${barras}s%*s] %d%%" "#" "$((50-barras))" "" "$percent"
}
Registro de interações em log:
LOG_FILE="interacoes.log"
registrar_log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> "$LOG_FILE"
}
# Exemplo de uso no menu
select opcao in "Executar" "Sair"; do
registrar_log "Usuário selecionou: $opcao"
case $REPLY in
1)
echo "Executando tarefa longa..."
for i in {1..10}; do
sleep 0.5
progresso 10 $i
done
echo -e "\nTarefa concluída!"
;;
2) break ;;
esac
done
8. Boas práticas e exemplos completos
Estrutura modular com funções:
#!/bin/bash
# Configurações
LOG_FILE="gerenciador.log"
declare -a tarefas=()
# Funções de utilidade
log() { echo "[$(date '+%H:%M:%S')] $*" >> "$LOG_FILE"; }
erro() { echo "ERRO: $*" >&2; log "ERRO: $*"; }
# Funções do menu
adicionar_tarefa() {
read -p "Descrição da tarefa: " descricao
if [[ -z "$descricao" ]]; then
erro "Descrição não pode ser vazia"
return 1
fi
tarefas+=("$descricao")
log "Tarefa adicionada: $descricao"
echo "Tarefa adicionada com sucesso!"
}
listar_tarefas() {
if [[ ${#tarefas[@]} -eq 0 ]]; then
echo "Nenhuma tarefa cadastrada."
return
fi
echo "=== TAREFAS ==="
for i in "${!tarefas[@]}"; do
echo "$((i+1)). ${tarefas[$i]}"
done
}
remover_tarefa() {
listar_tarefas
read -p "Número da tarefa a remover: " num
if [[ "$num" =~ ^[0-9]+$ ]] && (( num >= 1 && num <= ${#tarefas[@]} )); then
echo "Removendo: ${tarefas[$((num-1))]}"
unset 'tarefas[$((num-1))]'
tarefas=("${tarefas[@]}") # Reindexar array
log "Tarefa $num removida"
else
erro "Número inválido"
fi
}
# Menu principal
main() {
echo "=== GERENCIADOR DE TAREFAS ==="
PS3="Escolha uma opção: "
while true; do
select opcao in "Adicionar tarefa" "Listar tarefas" "Remover tarefa" "Sair"; do
case $REPLY in
1) adicionar_tarefa ;;
2) listar_tarefas ;;
3) remover_tarefa ;;
4)
echo "Saindo... Tarefas salvas em $LOG_FILE"
log "Usuário encerrou o programa"
exit 0
;;
*) echo "Opção inválida. Tente novamente." ;;
esac
break
done
echo
done
}
# Tratamento de interrupção
trap 'echo -e "\n\nPrograma interrompido."; exit 1' SIGINT SIGTERM
# Iniciar programa
main "$@"
Validação de argumentos de linha de comando:
# Uso: ./script.sh [-v] [-l arquivo_log]
while getopts "vl:" opt; do
case $opt in
v) set -x ;; # Modo verbose
l) LOG_FILE="$OPTARG" ;;
*) echo "Uso: $0 [-v] [-l arquivo_log]"; exit 1 ;;
esac
done
Referências
- GNU Bash Manual: Bourne Shell Builtins (read) — Documentação oficial do comando
readno Bash, incluindo todas as opções disponíveis - Advanced Bash-Scripting Guide: IFS and Internal Field Separator — Guia avançado sobre o uso de IFS para parsing de entrada no Bash
- Bash Hackers Wiki: The select command — Tutorial detalhado sobre o comando
selectpara construção de menus interativos - Linux Journal: Menu-Driven Bash Scripts — Artigo prático sobre criação de scripts com menus, validação e interação com usuário
- ShellCheck: Tool for analyzing shell scripts — Ferramenta online para análise e boas práticas em scripts shell, incluindo validação de entrada
- Bash Reference Manual: Signal Handling (trap) — Documentação oficial sobre captura e tratamento de sinais em scripts Bash
- Open Source.com: How to create a simple menu in Bash — Tutorial prático com exemplos de menus interativos usando
selectecase