Paths and directories

Pathname

Pathname wraps a path in an object that knows the operations. + appends a name with the right separator, and / joins two paths:

ruby -e 'require "pathname"; p Pathname("a") + "b.txt"'

#<Pathname:a/b.txt>

The object has dirname, basename, extname, exist?, read and write, so a method can take a path and call what it needs without converting to a string first:

ruby -e 'require "pathname"; p Pathname("a/b.txt").dirname; p Pathname("a/b.txt").basename.to_s'

#<Pathname:a>
"b.txt"

File.join

When a plain string is enough, File.join puts one separator between the parts and removes doubled ones:

ruby -e 'p File.join("a", "b")'

"a/b"

Do not build a path with "a" + "/" + "b": the separator is not / on Windows, and a path that starts with one absolute part swallows the rest. File.join and Pathname#+ are the two forms that work everywhere.

Dir.glob

Dir.glob matches a pattern against the names in a directory. In a folder with two text files and a Markdown file, it sorts the result, so two runs on the same folder print the same list:

ruby -e 'p Dir.glob("*.txt")'

["a.txt", "b.txt"]

The pattern speaks the shell language: * for one level, **/ for any depth, ? for one character and {a,b} for a choice. A recursive pattern is the shortest way to find files in a tree:

Dir.glob("**/*.txt").sort.each do |path| puts path end

ruby glob_demo.rb

a.txt
b.txt

A glob returns names, not contents, and it does not look inside hidden directories unless the pattern starts with a dot. A dotfile that a script must find needs Dir.glob(".*") or File::FNM_DOTMATCH.

The parts of a path

Three class methods answer the questions a file name asks, and each one works on a string:

ruby -e 'p File.basename("/x/y/report.txt"); p File.dirname(File.join("a", "b.txt")); p File.extname("a.tar.gz")'

"report.txt"
"a"
".gz"

extname returns the last extension only, and it includes the dot. A project that treats .tar.gz as one suffix strips it with its own rule, not with extname.

Temporary directories

A test or a conversion needs a folder it can fill and throw away. Dir.mktmpdir creates one in the system temporary area and removes it when the block ends:

ruby -e 'require "tmpdir"; Dir.mktmpdir { |d| p File.directory?(d) }'

true

Lesson 115 covers the cleanup guarantee. Pathname plus mktmpdir is the pair for a test that writes real files: the paths are within the temporary folder, and nothing a developer has on disk is touched.

What to watch for

A relative path in a script resolves against the working directory, not against the script file. ruby bin/report.rb run from the project root and from bin/ sees different files. Use __dir__ to anchor a path to the code: File.join(__dir__, "data.csv").

A path with a space is fine as a string and a problem in a shell command. Pass the path as a separate argument to system rather than building one command line.

Next: Binary files and streams

Article author: Arthur Isaev