File I/O
Ruby’s File and IO classes wrap the OS’s file APIs in a clean, exception-friendly interface. File.read, File.open with a block, foreach, Dir.glob, Pathname — pick the right primitive and file work stays idiomatic and crash-safe.
read, write, open, glob, Pathname
EXAMPLE
# 1) Reading a whole file
content = File.read('config.yml')
binary = File.binread('image.png')
# 2) Reading line by line — streaming
File.foreach('huge.log') do |line|
next unless line.include?('ERROR')
puts line.chomp
end
# foreach doesn't load the whole file into memory.
# 3) Reading all lines into an array
lines = File.readlines('config.yml') # includes trailing \n on each
lines.each(&:chomp!)
# 4) Writing — overwrite
File.write('out.txt', 'Hello world')
File.write('out.txt', "Line 1\nLine 2")
# Writing binary
File.binwrite('img.bin', binary_data)
# 5) Appending
File.open('log.txt', 'a') do |f|
f.puts "new entry at #{Time.now}"
end
# Or:
File.write('log.txt', 'new\n', mode: 'a')
# 6) Open with a block — auto-closes
File.open('data.csv', 'r') do |f|
f.each_line { |line| process(line) }
end
# The file is closed even if the block raises.
# 7) Different modes
# 'r' read (default)
# 'r+' read + write, preserve contents
# 'w' write, TRUNCATE
# 'w+' read + write, truncate
# 'a' append, create if missing
# 'a+' append + read
# 'b' binary (combine: 'rb', 'wb')
# 't' text (default on POSIX; explicit on Windows)
File.open('binary.dat', 'rb') { |f| f.read }
# 8) File metadata
File.exist?('data.csv')
File.file?('data.csv')
File.directory?('/tmp')
File.size('data.csv') # bytes
File.mtime('data.csv') # Time
File.atime('data.csv')
File.ctime('data.csv')
File.basename('/etc/passwd') # 'passwd'
File.dirname('/etc/passwd') # '/etc'
File.extname('/etc/profile.d/my.sh') # '.sh'
File.expand_path('~/config.yml') # absolute path
File.realpath('symlink.yml') # follows symlinks
# 9) Pathname — object-oriented file paths
require 'pathname'
path = Pathname.new('/etc/nginx/nginx.conf')
path.exist?
path.directory?
path.parent # Pathname '/etc/nginx'
path.basename # Pathname 'nginx.conf'
path.relative_path_from(Pathname.new('/etc')) # 'nginx/nginx.conf'
path.read # read shorthand
path.write('content')
path.expand_path
# Pathname#+ joins paths
base = Pathname.new('/etc/nginx')
base + 'conf.d' + 'site.conf' # /etc/nginx/conf.d/site.conf
# 10) Directory listing + glob
Dir.entries('/tmp') # ['.', '..', 'file1', 'file2']
Dir.children('/tmp') # without . and ..
Dir.glob('logs/*.log')
Dir.glob('logs/**/*.log') # recursive
Dir.glob(['*.rb', '*.md'])
Dir.glob('logs/*.log').sort
# Block form — streams matches
Dir.glob('**/*.rb') { |f| puts f }
# 11) Creating + removing
Dir.mkdir('new_dir')
Dir.mkdir('deep/nested', 0o755)
require 'fileutils'
FileUtils.mkdir_p('a/b/c') # like mkdir -p
FileUtils.rm_rf('a') # recursive remove
FileUtils.cp('a.txt', 'b.txt')
FileUtils.cp_r('dir1', 'dir2')
FileUtils.mv('a.txt', '/tmp/')
FileUtils.touch('marker.txt')
# 12) Temp files
require 'tempfile'
Tempfile.create('prefix') do |f|
f.write('hello')
f.close
process(f.path)
end # auto-deleted at end of block
require 'tmpdir'
Dir.mktmpdir do |tmp|
# tmp is auto-removed when block exits
end
# 13) CSV — common file format
require 'csv'
CSV.foreach('data.csv', headers: true) do |row|
puts row['name'], row['email']
end
CSV.open('out.csv', 'w') do |csv|
csv << ['name', 'email']
csv << ['Mara', 'mara@example.com']
end
# 14) JSON
require 'json'
config = JSON.parse(File.read('config.json'), symbolize_names: true)
File.write('config.json', JSON.pretty_generate(config))
# YAML — config in Rails-style apps
require 'yaml'
settings = YAML.safe_load_file('settings.yml', permitted_classes: [Symbol, Date])
# 15) Locks (advisory)
File.open('lock.txt', 'w') do |f|
f.flock(File::LOCK_EX) # exclusive lock
# critical section
f.flock(File::LOCK_UN)
end
# 16) Symlinks + permissions
File.symlink('/etc/nginx/nginx.conf', '/tmp/nginx.conf')
File.readlink('/tmp/nginx.conf') # '/etc/nginx/nginx.conf'
File.chmod(0o644, 'config.yml')
File.chown(1000, 1000, 'config.yml') # uid + gid
# 17) Common bugs
# • Forgetting to close — use block form or ensure { f.close }
# • Reading huge files with File.read → OOM; use foreach
# • Encoding errors on UTF-8 with BOM — File.read('f', encoding: 'bom|utf-8')
# • Path traversal via user input — File.expand_path + sanitisation; never trust
# • Race condition: File.exist? then open — use 'rescue Errno::ENOENT' instead
# • Tempfile not unlinking on crash — use Tempfile.create with block
# • CSV with quoted fields containing commas — handled correctly by stdlib; never split(',') yourself
# • Forgetting File::LOCK_NB if you want non-blocking lock attempt
# • Symlink loops with Dir.glob — use { File.symlink? } filter
Why it matters
Prefer the block form (File.open(...) { |f| ... }) so files close on exception, use foreach for streaming and Pathname for path manipulation, and reach for FileUtils and Tempfile for “Unix-y” recipes. Treat file I/O as the system boundary — validate paths, choose binary vs text explicitly, and rescue specific errors (ENOENT, EACCES).
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Discussion
Loading…