Skip to content
AgileLogics
6 min read

Working with Files

Prerequisites: Exceptions in depth, Blocks

Ruby's File class reads and writes files with a few short methods.

Writing and reading a whole file

File.write("notes.txt", "First line\nSecond line\n")

content = File.read("notes.txt")
puts content
p File.exist?("notes.txt")
p File.readlines("notes.txt").length
First line
Second line
true
2

File.write replaces the file's contents. To add to the end instead, pass mode: "a" (append): File.write("notes.txt", "Third line\n", mode: "a").

Reading line by line

File.read loads the entire file into memory. For large files, read one line at a time with File.foreach:

File.write("scores.txt", "ada,90\nlinus,75\ngrace,88\n")

File.foreach("scores.txt") do |line|
  name, score = line.chomp.split(",")
  puts "#{name.capitalize}: #{score}"
end

p File.readlines("scores.txt", chomp: true)
Ada: 90
Linus: 75
Grace: 88
["ada,90", "linus,75", "grace,88"]

File.open with a block

For more control, open the file with a block. Ruby closes the file automatically when the block ends — even if an error is raised inside it:

File.open("log.txt", "w") do |file|
  file.puts "Started"
  file.puts "Finished"
end

puts File.read("log.txt")
Started
Finished

Common modes: "r" read (default), "w" write (replace), "a" append.

Paths and folders

require "fileutils"

FileUtils.mkdir_p("reports/2025")
File.write(File.join("reports", "2025", "jan.txt"), "data")
p Dir.glob("reports/**/*.txt")
p File.basename("reports/2025/jan.txt", ".txt")
p File.extname("photo.jpeg")
FileUtils.rm_rf("reports")
["reports/2025/jan.txt"]
"jan"
".jpeg"

File.join builds paths with the right separator; Dir.glob finds files by pattern (** means "any folder depth").

Common mistakes

  • Reading a file that doesn't exist: No such file or directory @ rb_sysopen - missing.txt (Errno::ENOENT). Check with File.exist? or rescue Errno::ENOENT.
  • Opening a file with File.open without a block and forgetting to call close.
  • Relative paths are relative to where you ran the script, not to where the script lives. Use File.join(__dir__, "data.txt") for files next to the script.

Practice

Write a list of three shopping items to shopping.txt, one per line, then read it back and print each item numbered (1. milk).

Show solution
File.write("shopping.txt", %w[milk eggs bread].join("\n"))

File.readlines("shopping.txt", chomp: true).each_with_index do |item, i|
  puts "#{i + 1}. #{item}"
end