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:
Dir.rmdir / Dir.unlink / Dir.delete
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.