iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

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.

Example

Example
File.write('out.txt', "hi\n")
puts File.read('out.txt')
Try it Yourself »

Discussion

Loading…