Entrada e Saída de Arquivos em Ruby: Um Guia Visual e Prático
Aprenda entrada e saída de arquivos em Ruby de forma visual: três jeitos de ler um arquivo (e quanta memória cada um usa), arquivos que não existem, modos de arquivo, escrever e acrescentar, salvar em JSON, pastas, quebras de linha e exercícios com respostas.

Nesta página
Variáveis são esquecidas: no instante em que o seu programa termina, tudo o que estava nelas some. Para guardar um recorde, uma lista de tarefas, um diário ou um arquivo de configurações para a próxima vez, o programa precisa escrever num arquivo. E, para usar dados que vieram de outro lugar, como uma planilha exportada, um log ou um documento de texto, ele precisa ler um.
Isso é entrada e saída de arquivos (em inglês, file I/O, de input/output), e o Ruby deixa tudo bem fácil. Neste guia você vai ler um arquivo de três jeitos diferentes (e ver quanta memória cada um realmente usa), lidar com arquivos que não existem, escrever e acrescentar, salvar dados de jogo em JSON, organizar pastas e descobrir uma surpresa do Windows com as quebras de linha. Exercícios com respostas fecham o artigo.
A visão geral

Todo trabalho com arquivos segue os mesmos três passos: abrir o arquivo, ler ou escrever e depois fechar. Como você vai ver, o Ruby pode abrir e fechar por você.
Todos os exemplos abaixo usam este arquivinho, arquivo.txt, na mesma pasta do script Ruby:
Este é um arquivo de exemplo
Com algum conteúdo dentro
Esta é a última linha :)As execuções de exemplo vêm do Ruby 3.3.
Lendo um arquivo: três jeitos

1. File.read: tudo de uma vez
conteudo = File.read("arquivo.txt")
puts conteudo
puts "Foram #{conteudo.length} caracteres"1Este é um arquivo de exemplo
2Com algum conteúdo dentro
3Esta é a última linha :)
4Foram 80 caracteresSimples e prático para arquivos pequenos, como configurações ou modelos de texto.
2. File.readlines: um array de linhas
linhas = File.readlines("arquivo.txt", chomp: true)
p linhas
puts "O arquivo tem #{linhas.size} linhas; a última é: #{linhas.last}"["Este é um arquivo de exemplo", "Com algum conteúdo dentro", "Esta é a última linha :)"]
O arquivo tem 3 linhas; a última é: Esta é a última linha :)Ótimo quando você precisa das linhas como uma lista: para contá-las, ordená-las ou sortear uma. O chomp: true tira a quebra de linha do fim de cada uma.
3. File.foreach: uma linha por vez
File.foreach("arquivo.txt").with_index(1) do |linha, numero_da_linha|
puts "#{numero_da_linha}: #{linha.chomp}"
end1: Este é um arquivo de exemplo
2: Com algum conteúdo dentro
3: Esta é a última linha :)O foreach entrega uma linha ao seu bloco, espera você terminar com ela e só então lê a próxima. O .with_index(1) numera as linhas a partir do 1.
Sobre aquela quebra de linha: chomp
Cada linha vem com o seu caractere de quebra de linha, \n, ainda grudado. O p mostra isso:
1File.foreach("arquivo.txt") do |linha|
2 p linha
3end
4p "Com algum conteúdo dentro\n".chomp1"Este é um arquivo de exemplo\n"
2"Com algum conteúdo dentro\n"
3"Esta é a última linha :)\n"
4"Com algum conteúdo dentro"O chomp corta fora. O puts não se importa, porque só acrescenta uma quebra de linha quando o texto ainda não termina com uma. Mas quando você compara uma linha com alguma coisa (linha == "olá") ou junta linhas, um \n esquecido causa bugs confusos.
Qual usar? Vamos medir
Os três leem o mesmo arquivo, então faz diferença qual você escolhe? Testamos com um arquivo de log grande: 146 MB, 2 milhões de linhas, contando as linhas que têm ERROR:

O File.foreach usou só 1 MB a mais que um programa Ruby que não faz nada. O File.readlines precisou de mais de 390 MB extras, porque monta 2 milhões de strings separadas de uma vez. Veja como o programa da medição contou os erros de cada jeito:
File.read(nome_do_arquivo).each_line.count { |linha| linha.include?("ERROR") }
File.readlines(nome_do_arquivo).count { |linha| linha.include?("ERROR") }
File.foreach(nome_do_arquivo) { |linha| quantidade_de_erros += 1 if linha.include?("ERROR") }Quando o arquivo não existe
E se o arquivo não existir? O Ruby para com um erro:
File.foreach("nao_existe.txt") do |linha|
puts linha
endfaltando.rb:1:in `foreach': No such file or directory @ rb_sysopen - nao_existe.txt (Errno::ENOENT)
from faltando.rb:1:in `<main>'Errno::ENOENT é o nome do erro: "Error, NO such ENTry" ("não existe essa entrada"). Há dois bons jeitos de lidar com isso:

Opção 1: verifique antes com File.exist?
1nome_do_arquivo = "exemplo.txt"
2
3if File.exist?(nome_do_arquivo)
4 File.foreach(nome_do_arquivo) do |linha|
5 puts linha.chomp
6 end
7else
8 puts "Desculpe, #{nome_do_arquivo} não existe."
9endDesculpe, exemplo.txt não existe.Opção 2: tente e trate o erro
1begin
2 File.foreach("nao_existe.txt") do |linha|
3 puts linha
4 end
5rescue Errno::ENOENT => erro
6 puts "Ops, esse arquivo não está aqui!"
7 puts erro.message
8 puts "Vamos seguir sem ele."
9endOps, esse arquivo não está aqui!
No such file or directory @ rb_sysopen - nao_existe.txt
Vamos seguir sem ele.O código que você tenta rodar fica entre o begin e o rescue. Se aquele erro específico acontecer, o Ruby pula para a parte do rescue em vez de travar, e o erro.message descreve o que deu errado.
Escrevendo um arquivo
Para escrever, abra o arquivo no modo de escrita, "w", e use puts nele, como se estivesse imprimindo na tela:
1File.open("novo.txt", "w") do |arquivo|
2 arquivo.puts "Olá, pessoal!"
3 arquivo.puts "Este vai ser um arquivo novinho!"
4 arquivo.puts "Só mais uma última linha"
5end
6
7puts File.read("novo.txt")Olá, pessoal!
Este vai ser um arquivo novinho!
Só mais uma última linhaDentro do bloco, arquivo é a sua conexão com o arquivo. Quando o bloco termina, o Ruby fecha o arquivo para você.
puts, print, write e <<
1File.open("saida.txt", "w") do |arquivo|
2 arquivo.puts "o puts acrescenta uma quebra de linha"
3 arquivo.print "o print não"
4 arquivo.print " (viu?)\n"
5 arquivo.write "o write funciona como o print, "
6 arquivo << "e o << pode ser encadeado" << "!\n"
7end
8puts File.read("saida.txt")o puts acrescenta uma quebra de linha
o print não (viu?)
o write funciona como o print, e o << pode ser encadeado!Atalhos de uma linha
Para tarefas rápidas, o File.write abre, escreve e fecha de uma vez só. Acrescente mode: "a" para acrescentar em vez de sobrescrever:
File.write("compras.txt", "leite\novos\n")
File.write("compras.txt", "pão\n", mode: "a")
puts File.read("compras.txt")leite
ovos
pãoModos de arquivo: cuidado com o "w"
O segundo argumento do File.open é o modo. Ele decide se você pode ler, escrever ou os dois e, o mais importante, o que acontece com o que já está no arquivo:

Os dois que você mais vai usar são "w" (começar um arquivo do zero) e "a" (acrescentar no fim). Trocar um pelo outro é o jeito clássico de perder dados:
1File.write("diario.txt", "Segunda: aprendi Ruby\n")
2
3File.open("diario.txt", "a") { |arquivo| arquivo.puts "Terça: aprendi E/S de arquivos" }
4puts "Depois de acrescentar com \"a\":"
5puts File.read("diario.txt")
6
7File.open("diario.txt", "w") { |arquivo| arquivo.puts "Quarta: ops" }
8puts "Depois de abrir com \"w\":"
9puts File.read("diario.txt")1Depois de acrescentar com "a":
2Segunda: aprendi Ruby
3Terça: aprendi E/S de arquivos
4Depois de abrir com "w":
5Quarta: ops
Deixe um bloco fechar o arquivo por você
Um arquivo aberto usa recursos, e o que você escreve pode ficar esperando num buffer até o arquivo ser fechado. Com um bloco, o Ruby fecha o arquivo sozinho quando o bloco termina, mesmo que aconteça um erro dentro dele:

Sem um bloco, fechar é responsabilidade sua:
1arquivo = File.open("manual.txt", "w")
2arquivo.puts "Lembre de me fechar!"
3arquivo.close
4puts "Fechado? #{arquivo.closed?}"
5puts File.read("manual.txt")Fechado? true
Lembre de me fechar!Funciona, mas se algo der errado entre o open e o close, o arquivo fica aberto. Prefira a forma com bloco.
Uma tarefa de verdade: ler dados e escrever um relatório
Este é o tipo de script pequeno para o qual a E/S de arquivos é perfeita. O notas.txt tem um aluno por linha:
1Ana,90
2Bruno,85
3Carla,77
4Diego,92
5Eva,68O script lê o arquivo, calcula a média e o melhor aluno, e escreve um relatório:
1total = 0
2melhor_nome = nil
3melhor_nota = -1
4
5File.foreach("notas.txt") do |linha|
6 nome, texto_da_nota = linha.chomp.split(",")
7 nota = texto_da_nota.to_i
8 total += nota
9 if nota > melhor_nota
10 melhor_nota = nota
11 melhor_nome = nome
12 end
13end
14
15quantidade_de_alunos = File.readlines("notas.txt").size
16media = total.to_f / quantidade_de_alunos
17
18File.open("relatorio.txt", "w") do |relatorio|
19 relatorio.puts "Alunos: #{quantidade_de_alunos}"
20 relatorio.puts "Média: #{media.round(1)}"
21 relatorio.puts "Melhor: #{melhor_nome} (#{melhor_nota})"
22end
23
24puts File.read("relatorio.txt")Alunos: 5
Média: 82.4
Melhor: Diego (92)Salvando dados com JSON
Texto simples é ótimo para linhas de palavras. Para dados estruturados, como um hash de recordes, as configurações de um app ou o arquivo de save de um jogo, use JSON, um formato de texto que quase toda linguagem de programação consegue ler:

1require "json"
2
3recordes = { "Ana" => 1200, "Bruno" => 950, "Carla" => 1430 }
4File.write("recordes.json", JSON.pretty_generate(recordes))
5puts File.read("recordes.json")
6
7carregados = JSON.parse(File.read("recordes.json"))
8campea, pontos = carregados.max_by { |nome, pontuacao| pontuacao }
9puts "Campeã: #{campea} com #{pontos} pontos"1{
2 "Ana": 1200,
3 "Bruno": 950,
4 "Carla": 1430
5}
6Campeã: Carla com 1430 pontosPastas e caminhos
As classes Dir e File do Ruby, mais a biblioteca fileutils, cuidam de pastas e nomes de arquivos:
1require "fileutils"
2
3FileUtils.mkdir_p("anotacoes/2026")
4File.write(File.join("anotacoes", "2026", "janeiro.txt"), "Feliz ano novo!\n")
5File.write(File.join("anotacoes", "2026", "fevereiro.txt"), "Mês curto.\n")
6
7Dir.glob("anotacoes/**/*.txt").sort.each do |caminho|
8 puts "#{caminho} (nome: #{File.basename(caminho, '.txt')}, extensão: #{File.extname(caminho)})"
9endanotacoes/2026/fevereiro.txt (nome: fevereiro, extensão: .txt)
anotacoes/2026/janeiro.txt (nome: janeiro, extensão: .txt)FileUtils.mkdir_pcria uma pasta, incluindo as pastas-mãe que faltarem.File.joinmonta um caminho com o separador certo.Dir.glob("anotacoes/**/*.txt")acha todos os arquivos.txtdentro deanotacoes, em qualquer subpasta.File.basenameeFile.extnamedesmontam um caminho.
Duas coisas que surpreendem
Acentos e emoji: caracteres vs. bytes
O Ruby lê e escreve texto em UTF-8, então acentos e emoji funcionam sem problema. Mas um caractere nem sempre é um byte:
1File.write("saudacoes.txt", "Olá, café com pão!\n")
2texto = File.read("saudacoes.txt")
3puts texto
4puts "Caracteres: #{texto.chomp.length}, bytes: #{texto.chomp.bytesize}"
5puts "Codificação: #{texto.encoding}"Olá, café com pão!
Caracteres: 18, bytes: 21
Codificação: UTF-8á, é e ã ocupam 2 bytes cada em UTF-8, e é por isso que 18 caracteres enchem 21 bytes.
Quebras de linha no Windows
Escreva as mesmas duas linhas em modo texto e em modo binário no Windows, e os arquivos saem com tamanhos diferentes:
1File.open("modo_texto.txt", "w") { |arquivo| arquivo.puts "um"; arquivo.puts "dois" }
2File.open("modo_binario.txt", "wb") { |arquivo| arquivo.puts "um"; arquivo.puts "dois" }
3puts "Modo texto: #{File.size('modo_texto.txt')} bytes"
4puts "Modo binário: #{File.size('modo_binario.txt')} bytes"
5p File.binread("modo_texto.txt")
6p File.read("modo_texto.txt")Execução de exemplo no Windows:
1Modo texto: 10 bytes
2Modo binário: 8 bytes
3"um\r\ndois\r\n"
4"um\ndois\n"
O Windows tradicionalmente termina as linhas com dois caracteres, \r\n. No modo texto, o Ruby traduz em silêncio, então o seu código sempre vê um \n simples. No macOS e no Linux, os dois arquivos têm 8 bytes. Use o modo binário ("rb", "wb") para imagens, PDFs e outros arquivos que não são texto, para o Ruby não "traduzir" bytes que nem são quebras de linha.
Hora de praticar
Crie o arquivo.txt com as três linhas do começo deste artigo e tente fazer estes antes de espiar as respostas.
Exercício 1: Adivinhe o conteúdo (aquecimento). Depois de rodar isto, o que fica em numeros.txt?
File.write("numeros.txt", "1\n")
File.write("numeros.txt", "2\n")
File.write("numeros.txt", "3\n", mode: "a")Exercício 2: Contador de palavras. Conte as linhas, as palavras e os caracteres do arquivo.txt, como faz o comando wc.
Exercício 3: Busca. Imprima cada linha do arquivo.txt que contenha a palavra linha, com o número dela.
Exercício 4: Diário. Escreva um método acrescenta_entrada(data, texto) que coloque uma linha como [2026-10-24] Comecei a aprender E/S de arquivos no diario.txt sem apagar as entradas anteriores.
Exercício 5: Em voz alta. Leia o arquivo.txt e escreva uma cópia em MAIÚSCULAS no gritando.txt, linha por linha.
Exercício 6: Tabela de recordes (desafio). Guarde os 3 melhores recordes em recordes.json. Escreva salva_recorde(nome, pontos), que carrega a lista (ou começa vazia se ainda não houver arquivo), acrescenta o novo recorde, mantém os 3 melhores e salva. Depois acrescente Ana 1200, Bruno 950, Carla 1430 e Diego 800, e imprima a tabela.
Quiz rápido
- Qual é a diferença entre os modos
"w"e"a"? - Qual método de leitura mantém o uso de memória estável num arquivo enorme?
- Por que usar
rescue Errno::ENOENTem vez derescue Exception? - O que o
chomptira? - Por que o
File.opencom bloco é mais seguro do que chamar oclosevocê mesmo?
Respostas
Exercício 1: O segundo File.write substituiu o 1 (o modo de escrita começa do zero), e o terceiro acrescentou:
1File.write("numeros.txt", "1\n")
2File.write("numeros.txt", "2\n")
3File.write("numeros.txt", "3\n", mode: "a")
4print File.read("numeros.txt")2
3Exercício 2: O split sem argumentos divide em qualquer espaço em branco, e isso dá as palavras:
1quantidade_de_linhas = 0
2quantidade_de_palavras = 0
3quantidade_de_caracteres = 0
4
5File.foreach("arquivo.txt") do |linha|
6 quantidade_de_linhas += 1
7 quantidade_de_palavras += linha.split.size
8 quantidade_de_caracteres += linha.length
9end
10
11puts "Linhas: #{quantidade_de_linhas}, palavras: #{quantidade_de_palavras}, caracteres: #{quantidade_de_caracteres}"Linhas: 3, palavras: 16, caracteres: 80Exercício 3:
1palavra_procurada = "linha"
2
3File.foreach("arquivo.txt").with_index(1) do |linha, numero_da_linha|
4 puts "#{numero_da_linha}: #{linha}" if linha.include?(palavra_procurada)
5end3: Esta é a última linha :)Só a linha 3 bate: nenhuma das outras contém "linha".
Exercício 4: O modo de acrescentar, "a", mantém o que já está lá:
1def acrescenta_entrada(data, texto)
2 File.open("diario.txt", "a") do |diario|
3 diario.puts "[#{data}] #{texto}"
4 end
5end
6
7acrescenta_entrada("2026-10-24", "Comecei a aprender E/S de arquivos")
8acrescenta_entrada("2026-10-25", "Escrevi o meu primeiro arquivo de log")
9puts File.read("diario.txt")[2026-10-24] Comecei a aprender E/S de arquivos
[2026-10-25] Escrevi o meu primeiro arquivo de logExercício 5: Abra a saída uma vez e faça a entrada passar por ela:
1File.open("gritando.txt", "w") do |saida|
2 File.foreach("arquivo.txt") do |linha|
3 saida.puts linha.chomp.upcase
4 end
5end
6
7puts File.read("gritando.txt")ESTE É UM ARQUIVO DE EXEMPLO
COM ALGUM CONTEÚDO DENTRO
ESTA É A ÚLTIMA LINHA :)Repare que o upcase do Ruby também cuida das letras acentuadas.
Exercício 6: Uma cláusula de guarda cuida da primeira execução, quando ainda não existe arquivo:
1require "json"
2
3ARQUIVO_DE_RECORDES = "recordes.json"
4
5def carrega_recordes
6 return [] unless File.exist?(ARQUIVO_DE_RECORDES)
7
8 JSON.parse(File.read(ARQUIVO_DE_RECORDES))
9end
10
11def salva_recorde(nome, pontos)
12 recordes = carrega_recordes
13 recordes << { "nome" => nome, "pontos" => pontos }
14 tres_melhores = recordes.sort_by { |recorde| -recorde["pontos"] }.first(3)
15 File.write(ARQUIVO_DE_RECORDES, JSON.pretty_generate(tres_melhores))
16end
17
18salva_recorde("Ana", 1200)
19salva_recorde("Bruno", 950)
20salva_recorde("Carla", 1430)
21salva_recorde("Diego", 800)
22
23carrega_recordes.each_with_index do |recorde, indice|
24 puts "#{indice + 1}. #{recorde['nome']}: #{recorde['pontos']}"
25end1. Carla: 1430
2. Ana: 1200
3. Bruno: 950Os 800 pontos do Diego não entraram no top 3. Rode de novo, e os recordes continuam lá: esse é justamente o propósito de salvar num arquivo.
Quiz: (1) O "w" apaga o arquivo e começa do zero; o "a" mantém o conteúdo e acrescenta no fim. (2) O File.foreach, que segura uma linha por vez. (3) Exception também pega os seus próprios bugs e até o Ctrl+C; trate só o erro que você espera. (4) A quebra de linha (\n, ou \r\n) no fim de um texto. (5) O Ruby fecha o arquivo quando o bloco termina, mesmo que aconteça um erro dentro dele.
Cola rápida
| Eu quero… | Escreva |
|---|---|
| Ler o arquivo inteiro | File.read("notas.txt") |
| Pegar um array de linhas | File.readlines("notas.txt", chomp: true) |
| Ir linha por linha (arquivos grandes) | File.foreach("notas.txt") { |linha| … } |
| Numerar as linhas | File.foreach(nome).with_index(1) { |linha, numero| … } |
| Verificar se um arquivo existe | File.exist?("notas.txt") |
| Lidar com um arquivo que não existe | begin … rescue Errno::ENOENT => erro … end |
| Escrever um arquivo novo | File.write("notas.txt", texto) ou File.open(nome, "w") { |arquivo| arquivo.puts … } |
| Acrescentar no fim | File.write(nome, texto, mode: "a") ou File.open(nome, "a") { … } |
| Salvar / carregar dados | File.write(nome, JSON.pretty_generate(dados)) / JSON.parse(File.read(nome)) |
| Criar pastas | FileUtils.mkdir_p("a/b/c") |
| Montar um caminho | File.join("anotacoes", "2026", "janeiro.txt") |
| Achar arquivos | Dir.glob("anotacoes/**/*.txt") |
| Ler ou escrever dados binários | modos "rb" / "wb", ou File.binread |
Resumindo
Arquivos são o jeito de os programas se lembrarem das coisas. Em Ruby, trabalhar com eles é curto e agradável:
- Três jeitos de ler:
File.read(uma string),File.readlines(um array),File.foreach(uma linha por vez, e o único cuja memória ficou estável no nosso teste com 146 MB). - Arquivos que não existem geram
Errno::ENOENT: verifique comFile.exist?ou trate esse erro específico comrescue. - Os modos importam:
"w"apaga,"a"acrescenta,"r"lê. Acrescentebpara arquivos binários. - Blocos fecham os arquivos para você, mesmo quando algo dá errado.
- O JSON salva dados estruturados que sobrevivem entre uma execução e outra.
- O texto é UTF-8 e, no Windows, o modo texto traduz em silêncio as quebras de linha
\r\n.
Para conhecer melhor a linguagem, leia O Que É a Linguagem de Programação Ruby?.
Leia a seguir

O Que É a Linguagem de Programação Rust? Um Guia Simples da Linguagem Rápida Que Não Deixa Você Travar
Um guia simples e sem jargões sobre o Rust, a linguagem tão rápida quanto o C que impede famílias inteiras de erros de memória antes de o programa rodar. Entenda a ideia de posse, por que gigantes da tecnologia o adotam e onde ele é usado.

O Que É a Linguagem de Programação Go? Um Guia Simples da Linguagem da Nuvem
Um guia simples e sem jargões sobre o Go, a linguagem simples e rápida que o Google criou para a nuvem. Entenda o que são goroutines, por que um programa Go é um arquivo só e como o Go passou a mover o Docker, o Kubernetes e boa parte da internet.

O Que É a Linguagem de Programação Ruby? Um Guia Simples da Linguagem Feita para a Felicidade
Um guia simples e sem jargões sobre o Ruby, a linguagem de programação feita para deixar os programadores felizes. Entenda por que o código Ruby parece inglês, como o Ruby on Rails move sites como GitHub e Shopify, e onde o Ruby brilha.
Comentários
Seja gentil e específico. Comentários com links de contas novas aparecem depois da análise de um moderador.