File I/O
Reviewed & published by Brayan K
Ruby is a dynamic, beginner-friendly programming language, and file input/output lets your programs persist data — reading from and writing to files on disk so work survives between runs.
Part of the free Ruby course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.
By the end of this lesson you'll write, read, and append files safely with the block form, and process them line by line.
What You'll Learn in This Lesson
- Writing files with File.open and "w" mode
- Why the block form auto-closes files
- Reading with read, readlines, and each_line
- Appending with "a" mode
- Stripping newlines with chomp
- Checking files with exist? and size
1️⃣ Writing, Reading, and Appending
Open a file with File.open(name, mode) and a block — the file closes automatically when the block ends. Use "w" to write (overwrites), "a" to append, and read with File.read or File.readlines.
# WRITE: "w" mode creates (or overwrites) a file.
# The block form auto-closes the file for you — always prefer it.
File.open("notes.txt", "w") do |file|
file.puts "First line"
file.puts "Second line"
end
# READ the whole file into one string:
content = File.read("notes.txt")
puts content
# -> First line
# -> Second line
# Read all lines into an ARRAY (one string per line):
lines = File.readlines("notes.txt")
puts "Line count: #{lines.length}" # -> Line count: 2
# APPEND with "a" mode (adds without erasing):
File.open("notes.txt", "a") do |file|
file.puts "Third line"
end
puts File.read("notes.txt").lines.count # -> 32️⃣ Processing Files Line by Line
For larger files, process one line at a time with each_line or File.foreach so you don't load everything into memory. Call chomp to drop the trailing newline, and check files first with File.exist?.
File.write("notes.txt", "first line\nsecond line\nthird line\n") # the file this example reads
# Read line by line — memory-friendly for big files.
File.open("notes.txt", "r") do |file|
file.each_line do |line|
puts line.chomp.upcase # chomp removes the trailing newline
end
end
# -> FIRST LINE / SECOND LINE / THIRD LINE
# Useful File checks before you read:
puts File.exist?("notes.txt") # -> true
puts File.size("notes.txt") > 0 # -> true
# A tiny real task: count non-empty lines.
count = 0
File.foreach("notes.txt") do |line|
count += 1 unless line.strip.empty?
end
puts "Non-empty lines: #{count}" # -> Non-empty lines: 3Your turn. Write a to-do list to a file, then read it back numbered. Confirm the TODO details, then run it locally.
# 🎯 YOUR TURN — replace each TODO, then run with: ruby todo.rb
# 1) Write three tasks to a file, one per line
tasks = ["Buy milk", "Walk dog", "Write code"]
File.open("todo.txt", "w") do |file| # 👉 TODO: "w" to create/overwrite
tasks.each { |task| file.puts task }
end
# 2) Read the file back and number each task
File.readlines("todo.txt").each_with_index do |line, i|
puts "#{i + 1}. #{line.chomp}" # 👉 chomp drops the newline
end
# ✅ Expected output:
# 1. Buy milk
# 2. Walk dog
# 3. Write codeMini-Challenge: A Line-Count Report
Write a file, then count its total and non-blank lines with File.foreach. Run with ruby report.rb locally.
# 🎯 MINI-CHALLENGE: a line-count report.
# Save as report.rb and run with: ruby report.rb
#
# 1. Write a few lines to "log.txt" (some blank, some with text).
# 2. Read it back with File.foreach.
# 3. Count total lines AND non-blank lines (use line.strip.empty?).
# 4. Print both counts.
#
# ✅ Example output (5 lines, 2 blank):
# Total lines: 5
# Non-blank: 3
# your code hereCommon Errors (and the fix)
- "Errno::ENOENT: No such file" — Reading a file that doesn't exist. Check File.exist? first, or create it.
- "w" erased my data — "w" overwrites. Use "a" to add without erasing.
- Double blank lines — Lines keep their \n; call chomp before puts.
- Forgetting to close (non-block form) — Without a block you must close; just use the block form.
- Wrong working directory — Relative paths are relative to where you run Ruby. cd into the right folder.
Pro Tips
- 💡 File.write shortcut: File.write("x.txt", "hi") writes a whole string in one call.
- 💡 foreach for big files: it streams lines, so memory stays flat regardless of file size.
- 💡 Structured data: use the built-in CSV and JSON libraries for tables and objects.
📋 Quick Reference — File I/O
| Concept | Ruby Syntax |
|---|---|
| Write (block) | File.open(f,"w") { |x| } |
| Quick write | File.write(f, str) |
| Read whole | File.read(f) |
| Read lines | File.readlines(f) |
| Stream lines | File.foreach(f) { } |
| Exists? | File.exist?(f) |
🎉 Lesson Complete!
- ✅ Open files with File.open(name, mode) and a block
- ✅ The block form auto-closes the file safely
- ✅ Modes: "r" read, "w" overwrite, "a" append
- ✅ read, readlines, and foreach read in different ways
- ✅ chomp removes trailing newlines
Practice quiz
Why prefer the block form of File.open?
- It is faster
- It reads files in parallel
- It automatically closes the file, even if an exception is raised
- It compresses the file
Answer: It automatically closes the file, even if an exception is raised. The block form passes you the file and closes it automatically when the block ends, even on errors.
What does the "w" file mode do?
- Opens for writing and truncates (overwrites) existing content
- Reads the file
- Appends to the end
- Opens in binary mode
Answer: Opens for writing and truncates (overwrites) existing content. "w" opens for writing and empties the file first, so it overwrites; it also creates the file if missing.
Which mode appends new content without erasing what's there?
- "r"
- "w"
- "x"
- "a"
Answer: "a". "a" opens for appending, adding to the end without truncating.
What does File.read return?
- An array of lines
- The entire file as one string
- One line at a time
- The file size
Answer: The entire file as one string. File.read loads and returns the whole file as a single string.
What does File.readlines return?
- An array with one string per line
- One big string
- The number of lines
- A lazy enumerator
Answer: An array with one string per line. File.readlines returns an array, one string per line — handy for indexing or counting.
Which is the memory-efficient way to process a large file line by line?
- File.read
- File.readlines
- File.foreach / each_line
- File.size
Answer: File.foreach / each_line. File.foreach and each_line stream one line at a time, keeping memory flat regardless of file size.
What does `chomp` do to a line read from a file?
- Uppercases it
- Removes a trailing newline
- Splits it into words
- Adds a newline
Answer: Removes a trailing newline. Each read line keeps its trailing ; chomp removes it so puts doesn't double-space.
Which check confirms a file exists before reading it?
- File.size
- File.read
- File.open
- File.exist?
Answer: File.exist?. File.exist?(name) returns true/false; checking it avoids Errno::ENOENT.
Which shortcut writes a whole string to a file in one call?
- File.put
- File.write(name, str)
- File.save
- File.append
Answer: File.write(name, str). File.write("x.txt", "hi") writes the string in a single call.
What error do you get reading a file that doesn't exist?
- ZeroDivisionError
- ArgumentError
- Errno::ENOENT (No such file)
- TypeError
Answer: Errno::ENOENT (No such file). Reading a missing file raises Errno::ENOENT; check File.exist? first or create it.
Continue this course
- Previous: Regular Expressions
- Next: Metaprogramming Intro — Write code that writes code with send, define_method, and method_missing
- Quick reference: Ruby cheat sheet
Frequently asked questions
Why should I use the block form of File.open?
File.open("x", "w") { |f| ... } passes the open file to the block and automatically closes it when the block ends — even if an exception is raised inside. That guarantees you never leak an open file handle, which can otherwise cause data loss (writes may stay buffered until close) or 'too many open files' errors. The alternative is calling File.open without a block and remembering to call f.close yourself, usually in an ensure clause. The block form does that bookkeeping for you, so it's the recommended style.
What do the file modes 'r', 'w', and 'a' mean?
They tell Ruby how to open the file. "r" is read-only and is the default. "w" opens for writing and truncates the file to empty first — so it overwrites any existing content, and creates the file if it doesn't exist. "a" opens for appending, adding new content at the end without erasing what's there. There are also combined modes like "r+" (read and write) and a "b" suffix for binary data, but r, w, and a cover the vast majority of everyday tasks.
What's the difference between File.read, readlines, and each_line?
File.read returns the entire file as one big string — simplest, but it loads everything into memory. File.readlines returns an array with one string per line, convenient when you want to index or count lines. each_line (and File.foreach) processes the file one line at a time without loading it all, which is the memory-efficient choice for large files. The rule of thumb: read for small files you want whole, readlines when you need an array, and foreach/each_line for big files or streaming work.
Why do my lines have extra blank lines or a stray newline?
Each line read from a file usually includes its trailing newline character ( ). If you then print it with puts — which adds its own newline — you get a double blank line. The fix is chomp, which removes a trailing newline: line.chomp. Conversely, when writing, file.puts adds a newline for you, while file.print does not. So mismatched newlines almost always come from either forgetting chomp on read or mixing puts and print on write.