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 e write 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.

Comentários (0)

Nenhum comentário:

Postar um comentário