File I/O in Ruby

· 4 min read · Updated March 7, 2026 · beginner
file io files beginner reading writing

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.