Ruby File I/O — File IO Dir CSV and JSON Parsing Explained
In this tutorial, you will learn about Ruby File I/O. We cover key concepts, practical examples, and best practices to help you master this topic.
Ruby file I/O covers File class operations for reading and writing, IO streams for buffered I/O, Dir for directory traversal, CSV parsing for tabular data, and JSON serialization for web data.
What You'll Learn
- Reading and writing files with File class
- Working with directories using Dir
- Parsing CSV files
- Serializing and deserializing JSON
- Best practices for file handling
Why It Matters
File I/O is essential for any real-world application. Durga Antivirus Pro reads files for malware scanning, writes logs, and parses threat databases. DodaZIP reads archives and writes compressed files. Doda Browser caches files and reads configuration. Without file I/O, programs can't persist data.
Real-World Use
Rails applications read configuration files, Process uploaded files, generate reports, and manage log files. Data Pipelines read CSV files, transform data, and write results back. Automation scripts process log files and configuration.
flowchart LR
A["File I/O"] --> B["Reading"]
B --> C["Writing"]
C --> D["Directories"]
D --> E["CSV & JSON"]
A:::current --> B
style A fill:#2563eb,stroke:#2563eb,color:#fff
style B fill:#dbeafe,stroke:#2563eb,color:#1e40af
style C fill:#dbeafe,stroke:#2563eb,color:#1e40af
style D fill:#dbeafe,stroke:#2563eb,color:#1e40af
style E fill:#dbeafe,stroke:#2563eb,color:#1e40af
Reading Files
Read Entire File
# Read all content at once
content = File.read("data.txt")
puts content
# Read as array of lines
lines = File.readlines("data.txt")
lines.each { |line| puts line.chomp }
Read Line by Line (Efficient for Large Files)
# File.foreach — processes one line at a time
File.foreach("large_file.txt") do |line|
puts line.chomp
end
# With block
File.open("data.txt", "r") do |file|
file.each_line { |line| puts line }
end
Read with Different Modes
# Read specific bytes
data = File.read("data.txt", 100) # First 100 bytes
# Read with encoding
content = File.read("data.txt", encoding: "UTF-8")
Writing Files
Write Entire Content
# Overwrite
File.write("output.txt", "Hello, World!")
# Append
File.write("output.txt", "\nAnother line", mode: "a")
Write Line by Line
File.open("output.txt", "w") do |file|
file.puts "Line 1"
file.puts "Line 2"
file.print "No newline"
file.write "Also no newline"
end
File Open Modes
# "r" — Read only (default)
# "w" — Write (overwrites)
# "a" — Append (adds to end)
# "r+" — Read and write (no truncation)
# "w+" — Read and write (truncates)
# "a+" — Read and append
# "b" — Binary mode (add to any: "wb")
File Existence and Information
# Check existence
puts File.exist?("data.txt") # true/false
puts File.file?("data.txt") # Is it a file?
puts File.directory?("data.txt") # Is it a directory?
# File information
puts File.size("data.txt") # Bytes
puts File.mtime("data.txt") # Last modified time
puts File.atime("data.txt") # Last access time
puts File.extname("data.txt") # ".txt"
puts File.basename("/path/to/file.txt") # "file.txt"
puts File.dirname("/path/to/file.txt") # "/path/to"
Working with Directories
# List directory contents
Dir.entries(".").each { |entry| puts entry }
# Glob patterns
ruby_files = Dir.glob("**/*.rb")
puts ruby_files.inspect
# Create directory
Dir.mkdir("new_dir") unless Dir.exist?("new_dir")
# Current directory
puts Dir.pwd
# Change directory
Dir.chdir("/tmp") { puts Dir.pwd } # Temporary change
Directory Traversal
# Recursive listing
Dir.glob("**/*") do |path|
next unless File.file?(path)
puts "#{path}: #{File.size(path)} bytes"
end
The CSV Library
require 'csv'
# Reading CSV
CSV.foreach("data.csv", headers: true) do |row|
puts "#{row['name']}: #{row['email']}"
end
# Parse string
data = "name,age\nAlice,25\nBob,30"
table = CSV.parse(data, headers: true)
puts table[0]['name'] # Alice
# Writing CSV
CSV.open("output.csv", "w") do |csv|
csv << ["name", "email", "age"]
csv << ["Alice", "alice@example.com", 25]
csv << ["Bob", "bob@example.com", 30]
end
CSV with Options
require 'csv'
# Custom delimiter (tab-separated)
CSV.foreach("data.tsv", col_sep: "\t") do |row|
puts row.inspect
end
# Write with custom options
CSV.open("data.csv", "w", write_headers: true,
headers: ["ID", "Name"]) do |csv|
csv << [1, "Alice"]
csv << [2, "Bob"]
end
The JSON Library
require 'json'
# Parse JSON string
json_string = '{"name": "Alice", "age": 25}'
data = JSON.parse(json_string)
puts data["name"] # Alice
# Parse from file
data = JSON.parse(File.read("data.json"))
# Generate JSON string
hash = { name: "Alice", age: 25, languages: ["Ruby", "Python"] }
json = JSON.generate(hash)
puts json
# {"name":"Alice","age":25,"languages":["Ruby","Python"]}
# Pretty print
puts JSON.pretty_generate(hash)
JSON File Handling
require 'json'
# Read JSON file
def read_config(path)
JSON.parse(File.read(path))
rescue Errno::ENOENT
{}
end
# Write JSON file
def write_config(path, data)
File.write(path, JSON.pretty_generate(data))
end
config = read_config("config.json")
config[:updated_at] = Time.now.to_s
write_config("config.json", config)
File Error Handling
# Handle file not found
begin
content = File.read("missing.txt")
rescue Errno::ENOENT => e
puts "File not found: #{e.message}"
rescue Errno::EACCES => e
puts "Permission denied: #{e.message}"
end
Tempfiles
require 'tempfile'
Tempfile.create do |file|
file.puts "Temporary data"
file.rewind
puts file.read
end # Automatically deleted
Common Mistakes
1. Not Closing Files
# Wrong — file stays open
file = File.open("data.txt", "r")
content = file.read
# file never closed
# Right — block automatically closes
content = File.open("data.txt", "r") { |f| f.read }
2. Using readlines for Large Files
# Bad — loads entire file into memory
lines = File.readlines("huge_file.txt")
# Good — processes line by line
File.foreach("huge_file.txt") { |line| process(line) }
3. Not Checking File Existence
# Wrong — crashes if file missing
content = File.read("config.json")
# Right
if File.exist?("config.json")
content = File.read("config.json")
else
content = "{}"
end
4. Using Wrong Mode (w vs a)
File.write("log.txt", "new data") # Overwrites!
File.write("log.txt", "new data", mode: "a") # Appends
5. Parsing Invalid JSON
# Wrong — crashes on invalid JSON
data = JSON.parse(bad_json)
# Right
begin
data = JSON.parse(bad_json)
rescue JSON::ParserError => e
puts "Invalid JSON: #{e.message}"
data = {}
end
6. Hardcoding File Paths
# Bad — breaks on different OS
path = "C:\\Users\\name\\file.txt" # Windows only
# Better — use File.join
path = File.join("data", "config", "settings.yml")
# Even better — use relative paths with __dir__
path = File.join(__dir__, "config.yml")
Practice Questions
1. What's the difference between File.read and File.foreach?
File.read loads the entire file content into memory as a single string. File.foreach processes one line at a time without loading the whole file, making it suitable for large files.
2. How do you append to a file instead of overwriting?
Use mode "a" (append): File.write("log.txt", "new line\n", mode: "a") or open with File.open("log.txt", "a").
3. What's the purpose of the block form of File.open?
The block form automatically closes the file when the block exits, even if an exception is raised. This prevents resource leaks.
4. How do you parse a CSV file with headers?
Use CSV.foreach("file.csv", headers: true) { |row| ... }. Access columns with row['column_name'].
Challenge: Write a script that reads a directory of CSV files and produces a combined JSON file.
Solution
require 'csv'
require 'json'
def combine_csv_to_json(input_dir, output_file)
all_data = []
csv_files = Dir.glob(File.join(input_dir, "*.csv"))
csv_files.each do |file|
CSV.foreach(file, headers: true) do |row|
all_data << row.to_h
end
puts "Processed #{File.basename(file)}: #{all_data.size} total rows"
end
File.write(output_file, JSON.pretty_generate(all_data))
puts "Written #{all_data.size} rows to #{output_file}"
end
combine_csv_to_json("./data", "./combined.json")
FAQ
{{< faq question="What is the difference between puts and write for files?" >}}
puts writes the string followed by a newline. write writes exactly what you give it without adding a newline. print is like write but may buffer differently.
{{< /faq >}}
{{< faq question="How do I read a file line by line efficiently?" >}}
Use File.foreach("file.txt") { |line| ... } or File.open("file.txt") { |f| f.each_line { |line| ... } }. These process one line at a time without loading the entire file.
{{< /faq >}}
{{< faq question="What happens if I open a file that doesn't exist?" >}}
For reading, Ruby raises Errno::ENOENT. For writing with "w" or "a", Ruby creates the file if it doesn't exist (assuming the parent directory exists).
{{< /faq >}}
{{< faq question="How do I work with binary files?" >}}
Add "b" to the mode: File.open("image.png", "rb"). For binary-safe write: File.open("output.bin", "wb") { |f| f.write(data) }.
{{< /faq >}}
{{< faq question="How do I safely handle temporary files?" >}}
Use the tempfile standard library. Tempfile.create creates a unique temp file and deletes it when the block exits, even on exceptions.
{{< /faq >}}
Try It Yourself
# file_io_demo.rb
require 'json'
require 'csv'
# Create a sample CSV
CSV.open("demo.csv", "w") do |csv|
csv << ["name", "age", "city"]
csv << ["Alice", 25, "New York"]
csv << ["Bob", 30, "London"]
csv << ["Charlie", 35, "Tokyo"]
end
# Read and display
puts "CSV Contents:"
CSV.foreach("demo.csv", headers: true) do |row|
puts " #{row['name']} is #{row['age']} from #{row['city']}"
end
# Convert to JSON
rows = []
CSV.foreach("demo.csv", headers: true) { |r| rows << r.to_h }
File.write("demo.json", JSON.pretty_generate(rows))
puts "\nJSON written to demo.json"
puts JSON.pretty_generate(rows)
Expected output:
CSV Contents:
Alice is 25 from New York
Bob is 30 from London
Charlie is 35 from Tokyo
JSON written to demo.json
[
{
"name": "Alice",
"age": "25",
"city": "New York"
},
...
]
What's Next
Now that you understand file I/O, learn about Exception Handling to write robust code that handles errors gracefully.
| Topic | Description | Link |
|---|---|---|
| Ruby Exception Handling | begin/rescue, ensure, raise | {{< ref "20-exception-handling" >}} |
| Ruby Enumerable | each, map, select, reduce, group_by | {{< ref "21-enumerable" >}} |
| Python File I/O | Compare Python file handling | Python |
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro