Question
How can I write a Ruby loop that executes a block of code for every file in a directory? The Ruby script is run from a different directory than the directory I want to inspect. I tried Dir.foreach, but could not get it working.
Short Answer
You will learn how Ruby reads directory entries, how to use Dir.foreach and Dir.each_child, and why constructing the correct path matters when a script runs from another directory. You will also learn how to skip directories and process only regular files.
Concept
A directory is not an array of files, but Ruby provides iterator methods that let you visit its entries one at a time.
Dir.foreach(path) calls a block once for each entry in the directory. An entry can be a file, a subdirectory, or special entries named . and ...
Dir.foreach("data") do |entry|
puts entry
end
The value in entry is only the entry name, such as report.txt; it is not the complete path data/report.txt. When you need to read, delete, rename, or test an entry, join the directory path and entry name first.
A script's current working directory is usually the directory from which the command was run, not necessarily the directory containing the script. This distinction is why a relative path can appear not to work. You can use:
- an absolute path, such as
"/Users/ada/project/data" - a path relative to the current working directory
- a path based on the script's location using
__dir__
For modern Ruby code, Dir.each_child is often more convenient because it does not yield . and ...
Mental Model
Think of a directory as a folder containing labeled items. Dir.foreach is like asking someone to read every label in the folder. They read not only normal labels such as invoice.pdf, but also two navigation labels: . means “this folder” and .. means “the parent folder.”
The label alone does not tell you where the item is stored. To open it, attach the folder's address to the label. File.join(directory, entry) is Ruby's safe way to make that complete address.
Syntax and Examples
Use Dir.each_child when you want each direct child of a directory and do not need to handle . and ...
require "fileutils"
folder = File.join(__dir__, "uploads")
Dir.each_child(folder) do |entry|
path = File.join(folder, entry)
next unless File.file?(path)
puts "Processing: #{path}"
# Put code that processes this file here.
end
__dir__ is the directory that contains the current Ruby source file. Therefore, File.join(__dir__, "uploads") refers to an uploads folder beside the script, even if you run the script from somewhere else.
If you use Dir.foreach, explicitly skip the special entries:
folder = File.join(__dir__, "uploads")
Dir.foreach(folder) do |entry|
next if entry == "." || entry == ".."
path = File.join(folder, entry)
next unless File.file?(path)
puts path
end
To process only files with a particular extension, check it after building the full path:
Dir.each_child(folder) do |entry|
path = File.join(folder, entry)
next unless File.file?(path)
next unless File.extname(path).downcase == ".csv"
puts "Importing #{entry}"
end
Step by Step Execution
Consider this directory:
uploads/
notes.txt
photo.jpg
archived/
And this code:
folder = File.join(__dir__, "uploads")
Dir.each_child(folder) do |entry|
path = File.join(folder, entry)
next unless File.file?(path)
puts "Found file: #{entry}"
end
Execution proceeds as follows:
__dir__gets the folder containing the Ruby script.File.join(__dir__, "uploads")creates the target directory path.Dir.each_child(folder)yieldsnotes.txt,photo.jpg, andarchivedone at a time.- For
notes.txt,pathbecomes the full path touploads/notes.txt.File.file?returnstrue, so Ruby prints the message. - For
photo.jpg, the same check succeeds and Ruby prints another message. - For
archived,File.file?returnsfalsebecause it is a directory.nextskips the rest of that block iteration.
The output is:
Found file: notes.txt
Found file: photo.jpg
Real World Use Cases
Directory iteration is useful whenever a program works with batches of files:
- Import every CSV file placed in an upload folder.
- Resize or validate images before publishing them.
- Generate reports from many log files.
- Move processed documents into an archive directory.
- Check a build directory for generated assets.
- Scan configuration files and load each one.
In each case, first choose the directory, then iterate through its entries, build full paths, and filter entries to the kind of file your program expects.
Real Codebase Usage
In real projects, developers usually make directory processing defensive and explicit.
Validate the directory first
folder = File.join(__dir__, "uploads")
abort "Folder does not exist: #{folder}" unless Dir.exist?(folder)
This gives a clear message instead of failing later with a less obvious error.
Use guard clauses to skip unwanted entries
Dir.each_child(folder) do |entry|
path = File.join(folder, entry)
next unless File.file?(path)
next unless File.extname(path) == ".json"
next if File.zero?(path)
process_json(path)
end
This keeps the main processing code focused on valid files.
Handle file-specific failures without stopping the batch
Dir.each_child(folder) do |entry|
path = File.join(folder, entry)
next unless File.file?(path)
begin
process_file(path)
rescue StandardError => error
warn "Could not process #{path}: #{error.message}"
end
end
Use a narrow error class when you know the expected failure type. Avoid rescuing errors silently: logging the path and message makes failures diagnosable.
Use a pattern when a glob is clearer
Dir.glob(File.join(folder, "*.csv")).each do |path|
import_csv(path)
end
Dir.glob is convenient when the filename pattern itself is the main filter.
Common Mistakes
Using only the entry name as a file path
This often fails when the current working directory is not the target folder:
Dir.foreach("uploads") do |entry|
puts File.read(entry) # Wrong: entry is only a name.
end
Build the full path instead:
Dir.foreach("uploads") do |entry|
next if entry == "." || entry == ".."
path = File.join("uploads", entry)
next unless File.file?(path)
puts File.read(path)
end
Forgetting . and .. with Dir.foreach
Dir.foreach includes both special entries. Skip them or use Dir.each_child.
Dir.foreach(folder) do |entry|
next if [".", ".."].include?(entry)
# Process entry
end
Treating subdirectories as regular files
A folder can contain nested directories. Check File.file?(path) before file operations such as File.read.
Assuming relative paths are relative to the script
This is unreliable:
folder = "uploads"
It is relative to Dir.pwd, the directory used to start Ruby. For files beside the script, prefer:
folder = File.join(__dir__, "uploads")
Manually concatenating path strings
Avoid this:
path = folder + "/" + entry
Use File.join(folder, entry). It is clearer and works correctly across operating systems.
Changing the working directory unnecessarily
Dir.chdir(folder) can make relative paths work, but it changes process-wide state and can confuse larger programs. Usually, keep the full path in a variable instead.
Comparisons
| Tool | Best use | Includes . and ..? | Result yielded |
|---|---|---|---|
Dir.foreach(folder) | Basic iteration over direct entries | Yes | Entry name |
Dir.each_child(folder) | Direct entries without special names | No | Entry name |
Dir.children(folder) | You need an array of direct entry names | No | Array of names |
Dir.glob("path/*.csv") | Process files matching a filename pattern | No | Matching paths |
Find.find(folder) | Recursively visit a directory tree | No | Paths, including nested entries |
Dir.foreach and Dir.each_child inspect only one directory level. They do not automatically visit files inside subdirectories.
Use Dir.children only when you truly need all names stored in an array. Iterators such as each_child can start processing immediately and avoid creating an unnecessary array.
Cheat Sheet
# A stable path based on the script location
folder = File.join(__dir__, "uploads")
# Preferred direct-child iteration
Dir.each_child(folder) do |entry|
path = File.join(folder, entry)
next unless File.file?(path)
process(path)
end
# Dir.foreach requires special-entry filtering
Dir.foreach(folder) do |entry|
next if entry == "." || entry == ".."
path = File.join(folder, entry)
end
# File checks
File.file?(path) # regular file?
File.directory?(path) # directory?
Dir.exist?(folder) # directory exists?
File.extname(path) # e.g. ".csv"
# Pattern matching
Dir.glob(File.join(folder, "*.txt")).each do |path|
process(path)
end
Rules to remember:
- An
entryis usually a name, not a complete path. - Use
File.jointo build paths. Dir.foreachyields.and..;Dir.each_childdoes not.- Relative paths are based on
Dir.pwd, not automatically on the script location. - Use
__dir__for a path relative to the current source file.
FAQ
How do I loop over every file in a Ruby directory?
Use Dir.each_child(folder), build a path with File.join, and keep only regular files with File.file?(path).
Why does Dir.foreach return . and ..?
They are special directory entries: . refers to the current directory and .. refers to its parent. Skip them manually or use Dir.each_child.
Does Dir.foreach return the full file path?
No. It yields names such as "report.txt". Combine each name with the directory path using File.join(folder, entry).
Why does my Ruby script not find a relative directory?
A relative path is resolved from the current working directory (Dir.pwd), which is normally where you ran the command. Use an absolute path or File.join(__dir__, "directory_name").
How do I iterate over only .txt files in Ruby?
Use Dir.glob(File.join(folder, "*.txt")), or iterate with Dir.each_child and check File.extname(path).
Does this search subdirectories too?
No. Dir.foreach and Dir.each_child inspect only direct entries. Use Find.find when you need recursive traversal.
Should I use Dir.chdir before iterating?
Usually no. Passing full paths is easier to reason about and does not change the working directory for the rest of the program.
Mini Project
Description
Create a small batch processor that scans an incoming folder beside the Ruby script and prints a preview of every non-empty .txt file. This resembles a simple import or validation step used before processing uploaded text files.
Goal
List valid text files and print the first line of each one without processing directories, empty files, or other extensions.
Requirements
Create an incoming directory beside the Ruby script.
Add at least two .txt files, including one with content.
Iterate through direct entries in the directory.
Process only non-empty regular files ending in .txt.
Print each processed filename and its first line.
Keep learning
Related questions
Add a Default Value to an Existing Column in a Rails Migration
Learn how to add or change a default value on an existing database column using Rails Active Record migrations safely.
Calling a Class Method from an Instance in Ruby
Learn how to call a class method from an instance in Ruby using self.class, with examples, pitfalls, and practical usage patterns.
Calling an Overridden Monkey-Patched Method in Ruby
Learn how to call the original method when monkey patching in Ruby, including alias_method patterns, examples, pitfalls, and practical usage.