Entrada e Saída em Ruby: Guia Completo de I/O
Entrada e Saída em Ruby: Guia Completo de I/O
Ruby oferece duas abordagens distintas para entrada e saída de dados: uma interface simples que usamos frequentemente para tarefas básicas, e um sistema de classes I/O mais robusto que nos dá controle total sobre operações de arquivo e rede. Neste tutorial, vamos dominar ambas as abordagens com exemplos práticos e técnicas avançadas.
Interface Simples vs. Classes I/O Dedicadas
A interface simples que conhecemos bem inclui métodos como gets
, print
, puts
e printf
. Estes métodos são implementados no módulo Kernel e estão disponíveis globalmente:
# Interface simples para I/O
print "Digite seu nome: "
nome = gets
puts "Olá, #{nome.chomp}!"
Para operações mais complexas, Ruby fornece a classe base IO
e suas subclasses File
e BasicSocket
. Essas classes oferecem controle granular sobre operações de entrada e saída.
Trabalhando com Arquivos: Abertura e Fechamento
Método Tradicional com File.new
# Criando um objeto arquivo com File.new
arquivo = File.new("arquivo_teste.txt", "r")
# Processamento do arquivo...
conteudo = arquivo.read
puts conteudo
# Importante: sempre fechar o arquivo
arquivo.close
Método Seguro com Blocos
O método File.open
com bloco é mais seguro e elegante. O arquivo é automaticamente fechado ao final do bloco, mesmo se ocorrer uma exceção:
# Método seguro com fechamento automático
File.open("arquivo_teste.txt", "r") do |arquivo|
conteudo = arquivo.read
puts conteudo
# Processamento adicional...
end # arquivo automaticamente fechado aqui
Modos de Abertura de Arquivo
Ruby suporta diversos modos de abertura. Os principais são:
- "r" - Leitura apenas (padrão)
- "w" - Escrita apenas (sobrescreve o arquivo)
- "r+" - Leitura e escrita
- "a" - Anexar ao final do arquivo
- "a+" - Leitura e anexar
Lendo Arquivos: Técnicas e Iteradores
Leitura Linha por Linha
# Lendo arquivo linha por linha
File.open("dados.txt") do |arquivo|
while linha = arquivo.gets
puts linha.chomp # chomp remove a quebra de linha
end
end
Iteradores Poderosos para Leitura
Ruby oferece iteradores especializados que tornam a leitura mais elegante:
# Lendo byte por byte
File.open("dados.txt") do |arquivo|
arquivo.each_byte.with_index do |byte, indice|
print "#{byte.chr}:#{byte} "
break if indice > 10
end
end
# Lendo linha por linha com each_line
File.open("dados.txt") do |arquivo|
arquivo.each_line do |linha|
puts "Processando: #{linha.dump}"
end
end
Separadores Personalizados
O método each_line
permite definir separadores personalizados além da quebra de linha padrão:
# Usando 'e' como separador
File.open("dados.txt") do |arquivo|
arquivo.each_line("e") do |segmento|
puts "Segmento: #{segmento.dump}"
end
end
Métodos Convenientes para Leitura Completa
# Lendo arquivo inteiro como string
conteudo_completo = IO.read("dados.txt")
puts "Tamanho: #{conteudo_completo.length} caracteres"
# Lendo arquivo como array de linhas
linhas = IO.readlines("dados.txt")
puts "Total de linhas: #{linhas.length}"
puts "Primeira linha: #{linhas[0].chomp}"
# Iterando sem abrir explicitamente
File.foreach("dados.txt") do |linha|
puts "Processando: #{linha.chomp}"
end
Escrevendo Arquivos: Técnicas e Melhores Práticas
Escrita Básica com puts e print
# Escrevendo em arquivo com puts (adiciona quebra de linha)
File.open("saida.txt", "w") do |arquivo|
arquivo.puts "Olá, mundo!"
arquivo.puts "Resultado: #{1 + 2}"
arquivo.puts "Data: #{Time.now}"
end
# Verificando o resultado
puts File.read("saida.txt")
Diferenças entre puts, print e write
File.open("comparacao.txt", "w") do |arquivo|
# puts adiciona quebra de linha automaticamente
arquivo.puts "Linha com puts"
# print não adiciona quebra de linha
arquivo.print "Texto sem quebra"
arquivo.print " continuando na mesma linha\n"
# write retorna número de bytes escritos
bytes_escritos = arquivo.write("Texto com write")
arquivo.puts " (#{bytes_escritos} bytes)"
end
Trabalhando com Dados Binários
Para dados binários, Ruby oferece três abordagens principais:
# Método 1: Usando literais com escape sequences
dados_binarios1 = "\001\002\003"
# Método 2: Construindo byte por byte
dados_binarios2 = ""
dados_binarios2 << 1 << 2 << 3
# Método 3: Usando Array#pack (mais flexível)
dados_binarios3 = [1, 2, 3].pack("c*")
# Escrevendo dados binários
File.open("dados.bin", "wb") do |arquivo|
arquivo.print dados_binarios3
end
Localização e Manipulação de Arquivos
Ruby fornece ferramentas úteis para localizar arquivos e orientar-se no sistema de arquivos:
# Informações sobre o arquivo atual
puts "Arquivo atual: #{__FILE__}"
puts "Diretório atual: #{__dir__}"
puts "Caminho absoluto: #{File.realpath(__FILE__)}"
# Verificando onde Ruby está procurando arquivos
begin
arquivo_local = "config.txt"
puts "Ruby procuraria em: #{File.realpath(arquivo_local)}"
rescue Errno::ENOENT
puts "Arquivo #{arquivo_local} não encontrado"
end
# Construindo caminhos relativos ao arquivo atual
caminho_config = File.join(__dir__, "config", "app.yml")
puts "Caminho para config: #{caminho_config}"
I/O com Streams: Técnicas Avançadas
O operador <<
permite anexar objetos a streams de saída de forma elegante:
# Usando o operador << para streams
quebra_linha = "\n"
$stdout << 99 << " balões vermelhos" << quebra_linha
# Função genérica que funciona com qualquer objeto que implemente <<
def escrever_log(destino, mensagem)
timestamp = Time.now.strftime("%Y-%m-%d %H:%M:%S")
destino << "[#{timestamp}] #{mensagem}\n"
end
# Funciona com arquivos, strings, arrays...
File.open("log.txt", "w") do |arquivo|
escrever_log(arquivo, "Aplicação iniciada")
escrever_log(arquivo, "Processamento concluído")
end
StringIO: I/O Virtual para Testes e Flexibilidade
StringIO é uma classe poderosa que simula operações de arquivo usando strings na memória, ideal para testes e situações onde você precisa de compatibilidade com APIs de arquivo:
require "stringio"
# Criando StringIO para leitura
entrada = StringIO.new("agora é\no tempo\nde aprender\nRuby!")
# Criando StringIO para escrita
saida = StringIO.new("", "w")
# Processando como se fossem arquivos reais
entrada.each_line do |linha|
saida.puts linha.reverse
end
# Obtendo o resultado
puts "Resultado processado:"
puts saida.string
Caso Prático: Sistema de Log Flexível
require "stringio"
class Logger
def initialize(destino = $stdout)
@destino = destino
end
def log(nivel, mensagem)
timestamp = Time.now.strftime("%H:%M:%S")
@destino << "[#{timestamp}] #{nivel.upcase}: #{mensagem}\n"
end
end
# Em produção: log para arquivo
File.open("app.log", "w") do |arquivo_log|
logger_producao = Logger.new(arquivo_log)
logger_producao.log("info", "Aplicação iniciada")
logger_producao.log("error", "Falha na conexão")
end
# Em testes: log para StringIO
log_teste = StringIO.new
logger_teste = Logger.new(log_teste)
logger_teste.log("debug", "Teste executado")
# Verificando logs de teste
puts "Logs de teste capturados:"
puts log_teste.string
Networking: Conectando-se ao Mundo
Sockets TCP para Comunicação de Baixo Nível
require "socket"
# Conectando a um servidor web local
cliente = TCPSocket.open("127.0.0.1", "www")
# Enviando requisição HTTP OPTIONS
cliente.send("OPTIONS /~usuario/ HTTP/1.0\n\n", 0)
# Lendo resposta
resposta = cliente.readlines
puts resposta
# Sempre fechar a conexão
cliente.close
HTTP de Alto Nível com Net::HTTP
require "net/http"
# Fazendo requisição HTTPS
uri = URI("https://api.github.com/users/octocat")
Net::HTTP.start(
uri.host,
uri.port,
use_ssl: true
) do |http|
requisicao = Net::HTTP::Get.new(uri)
resposta = http.request(requisicao)
if resposta.code == "200"
puts "Dados do usuário recebidos:"
puts resposta.body[0, 200] # Primeiros 200 caracteres
else
puts "Erro na requisição: #{resposta.code}"
end
end
Simplificando com open-uri
require "open-uri"
# Forma mais simples para URLs HTTP/HTTPS
url = "https://httpbin.org/json"
begin
URI.open(url) do |arquivo_remoto|
conteudo = arquivo_remoto.read
puts "Conteúdo JSON recebido:"
puts conteudo[0, 150]
end
rescue OpenURI::HTTPError => erro
puts "Erro HTTP: #{erro.message}"
rescue SocketError => erro
puts "Erro de rede: #{erro.message}"
end
Tratamento de Erros e Melhores Práticas
Operações de I/O são sujeitas a falhas, portanto sempre devemos incluir tratamento de erros adequado:
def processar_arquivo_seguro(nome_arquivo)
begin
File.open(nome_arquivo, "r") do |arquivo|
contador_linhas = 0
arquivo.each_line do |linha|
contador_linhas += 1
# Processamento da linha aqui
end
return { sucesso: true, linhas: contador_linhas }
end
rescue Errno::ENOENT
return { sucesso: false, erro: "Arquivo não encontrado" }
rescue Errno::EACCES
return { sucesso: false, erro: "Sem permissão de leitura" }
rescue IOError => e
return { sucesso: false, erro: "Erro de I/O: #{e.message}" }
end
end
# Testando a função
resultado = processar_arquivo_seguro("arquivo_inexistente.txt")
puts resultado
Conclusão: Dominando I/O em Ruby
Ruby oferece um sistema de I/O excepcionalmente rico e flexível. Começamos com métodos simples como gets
e puts
, exploramos as classes File
e IO
para controle granular, descobrimos o poder do StringIO
para testes e flexibilidade, e até mesmo nos aventuramos no networking com sockets e HTTP.
Os principais conceitos que dominamos incluem:
- Abertura segura de arquivos com blocos e tratamento automático de fechamento
- Múltiplas formas de leitura: linha por linha, byte por byte, ou arquivo completo
- Diferenças entre
puts
,print
ewrite
para escrita - Uso do operador
<<
para código flexível e reutilizável - StringIO para simulação de I/O em memória
- Comunicação de rede com sockets e APIs HTTP
- Tratamento robusto de erros para operações I/O
Com essas ferramentas, você está preparado para lidar com qualquer desafio de entrada e saída em Ruby, desde simples processamento de arquivos até comunicação complexa via rede. O sistema de I/O do Ruby combina simplicidade para tarefas básicas com poder suficiente para necessidades avançadas.