Files, IO and the standard library

Ruby · Practical · 12 min

Scripting and data jobs, one of Ruby's strengths, are mostly reading, transforming and writing files.

The simplest file operations are one-liners: File.read(path) returns the whole content, File.write(path, text) replaces it, and File.readlines(path, chomp: true) returns an array of lines. They are fine for small files.

For anything larger or longer-lived, use the block form of File.open, which closes the file even if an exception is raised:

File.open("out.log", "a") { |f| f.puts "started" }

Modes: "r" read (default), "w" truncate and write, "a" append, "r+" read and write; add "b" for binary. File.foreach(path) { |line| ... } streams a file line by line in constant memory.

Paths: File.join("data", "x.csv"), File.exist?, File.basename, File.extname, Dir.glob("logs/**/*.log"), and Pathname for an object-oriented API. __dir__ is the directory of the current file, useful for locating data next to a script.

Standard library helpers you will use constantly:

  • require "json": JSON.parse(text, symbolize_names: true) and obj.to_json or JSON.pretty_generate(obj).
  • require "csv": CSV.foreach(path, headers: true) { |row| row["email"] }. Since Ruby 3.4 csv is a bundled gem, so add it to your Gemfile in Bundler projects.
  • require "fileutils": mkdir_p, cp, rm_rf.

Standard streams are $stdin, $stdout and $stderr; warn "msg" writes to stderr, and ARGV holds command-line arguments.

Example

require "json"
require "tmpdir"

Dir.mktmpdir do |dir|
  path = File.join(dir, "people.jsonl")
  File.open(path, "w") do |f|
    f.puts({ name: "Ada", langs: %w[ruby c] }.to_json)
    f.puts({ name: "Bo", langs: %w[go] }.to_json)
  end

  people = File.foreach(path).map { JSON.parse(it, symbolize_names: true) }
  p people.first                         # {name: "Ada", langs: ["ruby", "c"]}
  p people.sum { it[:langs].size }       # 3

  File.write(File.join(dir, "summary.json"), JSON.pretty_generate(people))
  p Dir.glob("#{dir}/*").map { File.basename(it) }.sort
  # ["people.jsonl", "summary.json"]
  p File.extname(path)                   # ".jsonl"
end

Remember

  • Use File.open with a block so files are always closed
  • File.foreach streams large files line by line
  • JSON.parse(text, symbolize_names: true) gives symbol keys

Watch out for

  • Calling File.open without a block and forgetting to close the file.

Check yourself

  1. Why prefer File.open(path) { |f| ... } over f = File.open(path)?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.ruby-lang.org/en/3.4/File.html

Builds on

Unlocks

Nothing builds on this yet.