File I/O in Ruby
File I/O is a fundamental skill for any Ruby developer. Whether you are processing log files, reading configuration data, or generating reports, Ruby provides a clean and powerful API for working with files. In this tutorial, you will learn how to read from and write to files, work with paths safely, and follow best practices that will make your code robust and reliable.
Reading Files in Ruby
Ruby makes reading files straightforward with several approaches depending on your needs. The simplest way to read an entire file into memory is with File.read:
# Read entire file contents as a string
content = File.read("config.txt")
puts content
# Read file into an array of lines
lines = File.readlines("config.txt")
puts lines.first
For large files, reading line-by-line is memory-efficient and prevents loading the entire file into RAM. The IO.foreach method processes one line at a time:
# Process file line by line - memory efficient
IO.foreach("large_log.txt") do |line|
puts line if line.include?("ERROR")
end
If you need more control over the reading process, open the file manually with a block:
# Open file and read with control
File.open("input.txt", "r") do |file|
puts file.gets # Read one line
puts file.read(10) # Read 10 bytes
puts file.pos # Current position in file
end
The block form of File.open automatically closes the file when done, even if an error occurs.
Writing Files in Ruby
Writing files follows similar patterns. Use File.write for quick one-liners:
# Write string to file (overwrites existing content)
File.write("output.txt", "Hello, World!")
# Append to existing file
File.write("log.txt", "New log entry\n", mode: "a")
# Write array of lines
lines = ["line one", "line two", "line three"]
File.write("data.txt", lines.join("\n"))
For more complex writing scenarios, open the file with a block:
# Write using block form - automatically closes file
File.open("output.txt", "w") do |file|
file.puts "First line"
file.puts "Second line"
file.write "Raw string without newline"
end
The file mode determines how the file is opened: “r” for read, “w” for write (truncates), “a” for append.
Working with File Paths
Working with paths correctly ensures your code works across different operating systems. Ruby provides helpful methods:
# Join path components - handles OS differences automatically
path = File.join("data", "config", "settings.txt")
# Expand relative path to absolute path
absolute_path = File.expand_path("config.txt")
# Get file information
puts File.size("large_file.txt")
puts File.dirname("/home/user/project/file.txt")
puts File.extname("photo.jpg")
puts File.basename("/path/to/file.txt")
# Check file properties
File.exist?("config.txt")
File.directory?("folder")
File.file?("config.txt")
File.readable?("config.txt")
File.writable?("config.txt")
Using FileUtils for Common Operations
For common file and directory operations, Rubys FileUtils module provides a rich set of methods:
require "fileutils"
# Directory operations
FileUtils.mkdir_p("data/exports")
FileUtils.cp("source.txt", "backup.txt")
FileUtils.mv("old.txt", "new.txt")
FileUtils.rm_rf("temp_files")
FileUtils.touch("access.log")
Best Practices
Following these practices will make your file handling code more robust:
Always use blocks with File.open when possible. The block ensures the file is closed even if an error occurs:
# Good: Block form - file guaranteed to close
File.read("data.txt") do |file|
# work with file
end
Handle errors gracefully with begin/rescue/ensure:
begin
content = File.read("important.txt")
rescue Errno::ENOENT
puts "File not found"
rescue Errno::EACCES
puts "Permission denied"
end
Use proper encoding when working with text files:
File.read("data.txt", encoding: "UTF-8")
File.write("output.txt", "Content", encoding: "UTF-8")
Working with Temporary Files
For temporary data, Rubys Tempfile class is ideal:
require "tempfile"
tempfile = Tempfile.new("my_app")
tempfile.write("Temporary data")
tempfile.close
puts tempfile.path
tempfile.unlink
Summary
You have learned the fundamentals of file I/O in Ruby: reading files with File.read and IO.foreach, writing with File.write and File.open, path handling with File.join and File.expand_path, FileUtils for common operations, and best practices including using blocks, error handling, and proper encoding.
Binary Files
Ruby handles binary files the same way as text files, but you need to specify the binary mode:
# Read binary file
File.binread("image.png")
# Write binary data
File.binwrite("output.bin", binary_data)
# Read binary file with block
File.open("image.png", "rb") do |file|
data = file.read
puts data.bytesize
end
When working with binary files, always use “b” in the mode string to prevent Ruby from trying to interpret the data as text.
Reading CSV Files
For structured data like CSV files, Rubys built-in CSV library makes parsing straightforward:
require "csv"
# Read CSV file
CSV.foreach("data.csv", headers: true) do |row|
puts row["name"]
puts row["email"]
end
# Parse CSV from string
csv_data = "name,age\nAlice,30\nBob,25"
CSV.parse(csv_data, headers: true) do |row|
puts row["name"]
end
Working with JSON
Reading and writing JSON files is equally straightforward with the json library:
require "json"
# Read JSON file
data = JSON.parse(File.read("config.json"))
# Write JSON file
File.write("output.json", JSON.pretty_generate(data))
File Locking
When multiple processes might access the same file, file locking prevents conflicts:
File.open("shared.txt", "r+") do |file|
file.flock(File::LOCK_EX) # Exclusive lock
# Read or write safely
file.flock(File::LOCK_UN) # Release lock
end
Ruby supports shared locks (LOCK_SH) for reading and exclusive locks (LOCK_EX) for writing.