rubyguides

Dir

The Dir module provides methods for manipulating directories in Ruby. You can read directory contents, change the current working directory, create and remove directories, and more. It is a practical tool when a script needs to inspect the filesystem without pulling in extra libraries.

Overview

Dir is part of Ruby’s standard library for working with the file system:

  • Read: List files and subdirectories
  • Navigate: Change the current working directory
  • Create: Make new directories
  • Delete: Remove empty directories
  • Query: Check if paths exist or are directories

These examples move from simple listings to navigation and iteration so you can see where Dir is read-only and where it changes state. That makes it easier to choose the right method before you start touching the filesystem.

Reading directory contents

Dir.entries

Returns an array of all entries in a directory, including . and ..:

Dir.entries('/path/to/dir')
# => [".", "..", "file1.txt", "file2.rb", "subdir"]

Dir.entries returns everything including the navigation markers . and .., which you then have to filter out yourself. When you just want the actual contents, Dir.children is the cleaner choice — it strips those two entries automatically so your loop only touches real files and folders:

Dir.children

Returns entries excluding . and ..:

Dir.children('/path/to/dir')
# => ["file1.txt", "file2.rb", "subdir"]

Dir.children returns entries without . and .., which is cleaner than entries when you plan to iterate over the results. If your directory contains filenames with non-ASCII characters, you can pass an encoding: keyword to ensure Ruby reads them correctly:

Dir.children('/path/to/dir', encoding: 'UTF-8')

Once you have the raw directory listing, the next step is usually filtering by file type or name pattern. Dir.glob handles that directly with shell-style wildcards, so you can skip the select or grep step that entries or children would require:

Dir.glob

Find files matching a pattern:

# All Ruby files
Dir.glob('*.rb')
# => ["main.rb", "helper.rb", "app.rb"]

# Recursive search
Dir.glob('**/*.rb')
# => ["lib/a.rb", "lib/b.rb", "spec/c.rb"]

# Multiple patterns
Dir.glob(['*.rb', '*.md'])

The bracket form is handy in quick scripts because it keeps the pattern close to the call site and avoids a second method name.

The patterns you pass to glob can include recursive wildcards like **/*.rb and accept multiple patterns in an array. For quick one-off lookups, Ruby also provides the bracket syntax as a shorthand that reads more concisely in simple scripts:

Dir.[] shortcut

Shortcut for Dir.glob:

Dir['*.rb']
Dir['**/*.txt']

Changing directories

Once you can list directory contents, the next common operation is navigating between directories. Dir.chdir changes the working directory, and it accepts either a block for scoped changes or a plain call for permanent moves:

Dir.chdir

Change the current working directory:

Dir.pwd          # => "/home/user"
Dir.chdir('/tmp')
Dir.pwd          # => "/tmp"

The Dir.[] shorthand is equivalent to Dir.glob but keeps the call site compact. After listing files, you often need to move between directories. Dir.chdir changes the working directory, and the block form is the safer way to do it because Ruby restores the original directory automatically when the block finishes:

With a block, temporarily changes directory:

Dir.chdir('/tmp') do
  # We're now in /tmp
  Dir.pwd
end
# Back to original directory

That block form is safer when a script only needs a temporary working directory. Ruby restores the original location as soon as the block finishes, so the surrounding code does not need to remember to switch back manually.

Without arguments, chdir uses the HOME environment variable:

Dir.chdir   # Changes to HOME directory

The block form of chdir keeps the directory change scoped, so the rest of your script never sees the temporary location. After navigating, Dir.pwd and its alias Dir.getwd tell you where you are at any point:

Dir.pwd / Dir.getwd

Get the current working directory:

Dir.pwd     # => "/home/user/project"
Dir.getwd   # => "/home/user/project"

Creating and deleting directories

Both pwd and getwd return the same value — they are interchangeable. Once you know where you are on the filesystem, creating directories with mkdir is straightforward, and you can pass an optional permission mask as the second argument:

Dir.mkdir

Create a directory:

Dir.mkdir('new_folder')
Dir.mkdir('path/to/deep/folder')

Dir.mkdir can create nested directory trees in one call when you pass a path with intermediate directories. If you need to control who can access the new directory, pass an octal permission mask like 0755 as the second argument — this sets read, write, and execute bits for the owner, and read/execute for group and others:

Dir.mkdir('restricted', 0755)

mkdir accepts an octal permission mask like 0755 as its second argument, controlling who can read, write, or enter the new directory. When you are done with a directory, rmdir (and its aliases unlink and delete) remove it — but only if it is empty:

Remove an empty directory:

Dir.rmdir('empty_folder')
Dir.unlink('empty_folder')   # Alias
Dir.delete('empty_folder')   # Alias

These methods raise an error if the directory is not empty.

Once you know how to create and remove directories, the next step is usually checking whether the path is ready first. The query methods below are the read-only checks that help you avoid accidental changes.

Querying directories

All three method names do the same thing: they remove an empty directory and raise an error if the directory still contains files. Before creating or removing anything, it is safer to check whether the path exists and whether it is actually a directory. Dir.exist? answers that question:

Dir.exist? / Dir.exists?

Check if a path is a directory:

Dir.exist?('/path/to/dir')   # => true or false
Dir.exists?('/path/to/dir')  # Alias (deprecated)

Dir.exist? returns true only if the path exists and is a directory; Dir.exists? is a deprecated alias you should avoid. If the directory exists and you need to know whether it has any contents before removing it, Dir.empty? gives you a quick boolean answer:

Dir.empty?

Check if a directory is empty:

Dir.mkdir('temp_dir')
Dir.empty?('temp_dir')       # => true
Dir.rmdir('temp_dir')

A fresh directory created with mkdir is always empty, so Dir.empty? returns true. The Dir.home method is a convenient way to get the current user’s home directory without parsing environment variables, and it also accepts a username argument for looking up another user’s home:

Dir.home

Get the home directory:

Dir.home           # => "/home/user"
Dir.home('other')  # => "/home/other"

These checks are especially useful when a path may not exist yet or when your script runs on different machines. A quick existence check usually keeps the error handling simpler than rescuing filesystem failures later.

Working with Dir objects

Dir#read advances an internal cursor through the directory entries, returning nil when all entries have been consumed. After reading entries individually, you might want to manage the full lifecycle of a directory handle. Dir.new opens a directory for iteration and gives you methods like #seek and #rewind to control the read position:

Dir.new

Open a directory for iteration:

dir = Dir.new('/path/to/dir')
dir.path    # => "/path/to/dir"
dir.each { |entry| puts entry }
dir.close

The object form is useful when you want to manage directory reading step by step. The block form below is usually easier because Ruby closes the handle automatically.

Dir.new returns a directory handle that you must close manually with #close. The preferred pattern is Dir.open with a block, which automatically closes the handle when the block finishes — the same scoped pattern used by File.open and Dir.chdir:

Dir.open (Preferred)

Using Dir.open with a block automatically closes the directory:

Dir.open('/path/to/dir') do |dir|
  dir.each { |entry| puts entry }
end
# Automatically closed

The block form of Dir.open is safer because you cannot forget to close the handle. If you need to control iteration manually rather than with each, use Dir#read to pull entries one at a time from an open directory:

Dir#read

Read the next entry from an open directory:

dir = Dir.new('/path/to/dir')
dir.read    # => "first_file.txt"
dir.read    # => "second_file.rb"
dir.close

Dir.new returns a handle that supports #each, #read, and positional control. After iterating through entries, #seek lets you jump back to any position in the directory listing, which is useful when you need to re-read specific entries without closing and reopening the handle:

Dir#seek

Seek to a specific position:

Dir.open('/path/to/dir') do |dir|
  dir.read           # First entry
  dir.seek(0)        # Go back to start
  dir.read           # First entry again
end

With #seek, you can jump to any recorded position in the directory stream. A simpler option when you just want to start over from the beginning is #rewind, which resets the read position back to the first entry without needing to track any offset:

Dir#rewind

Rewind to the beginning:

Dir.open('/path/to/dir') do |dir|
  dir.each { |e| }
  dir.rewind
  dir.each { |e| }
end

These methods are handy when you need to inspect a directory more than once. Reading, seeking, and rewinding all stay inside the same open handle, which keeps the example focused on the directory stream itself.

Iterating over directories

After rewinding, calling #each or #read again will produce the same entries from the start. The class-level methods foreach and each_child offer a more convenient iteration interface when you do not need manual position control:

each_child is the most convenient iterator when you care about the actual files and directories and want to ignore the navigation markers. If you need the full listing with . and .. included, foreach is the class-level equivalent of Dir#each:

Dir.foreach

Iterate over all entries:

Dir.foreach('/path/to/dir') { |entry| puts entry }

Dir.foreach takes a block and yields each entry, closing the handle automatically when done. For cases where you want to open a directory once and iterate multiple times, Dir#each on a Dir object gives you reusable iteration over the same open handle:

Dir.each

Iterate using a Dir object:

dir = Dir.new('/path/to/dir')
dir.each { |entry| puts entry }
dir.close

Dir#each iterates over all entries including . and .., which means you often need an extra condition or guard clause to skip those two markers. The each_child method handles that filtering for you automatically, returning only the real directory contents without the navigation entries:

Dir.each_child

Iterate excluding . and ..:

Dir.each_child('/path/to/dir') { |entry| puts entry }

The child-only form is often the most convenient for scripts that care about the actual entries and want to ignore the navigation markers. It keeps the loop output a little cleaner.

The child-only form keeps the loop output cleaner and eliminates the need for a guard clause. With the directory iteration tools covered, the practical examples below show how these methods combine in real scripts:

Practical examples

Find all Ruby files in a project

ruby_files = Dir.glob('**/*.rb')
ruby_files.each { |f| puts f }

This pattern is a good fit for scripts that need a quick inventory of source files. Because glob returns an array, you can keep chaining array methods after it if you need extra filtering or ordering.

The glob pattern with **/*.rb finds every Ruby file in the project tree, and because glob returns an array, you can chain additional array methods for sorting or further filtering. To see what kinds of files are in a directory at a glance, group entries by their extension:

List files by extension

files = Dir.children('.').group_by { |f| File.extname(f) }
files.each { |ext, names| puts "#{ext}: #{names.join(', ')}" }

Grouping by extension gives you a simple directory summary without walking the tree yourself. It is a neat pattern when you want to report what kinds of files are in the current folder, and it works well as a first step before deciding which files to process. When your script needs to change directories temporarily, the pattern below keeps you from losing the original location:

Safe directory navigation

original = Dir.pwd

begin
  Dir.chdir('/tmp')
  # Do work in /tmp
ensure
  Dir.chdir(original)  # Always restore
end

The begin...ensure pattern works but adds several lines of boilerplate that obscure the actual logic. The block form of chdir is the cleaner approach because Ruby restores the original directory automatically when the block finishes, even if an exception is raised inside it. This is the same scoped pattern used by File.open and Dir.open:

Dir.chdir('/tmp') do
  # Work in /tmp
end
# Automatically back to original

That final example shows the safest form for temporary navigation. The block keeps the state change local, which is useful in scripts that run several filesystem steps in a row.

See Also

  • File — work with files
  • ERB — template processing (uses Dir for template paths)
  • FileUtils — higher-level file and directory operations
  • IO — open files and pipes (often paired with Dir for path traversal)