Question

Iterate Through Files in a Directory in Ruby

rubydirectoryfilesystems

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.

Take Quiz

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:

  1. __dir__ gets the folder containing the Ruby script.
  2. File.join(__dir__, "uploads") creates the target directory path.
  3. Dir.each_child(folder) yields notes.txt, photo.jpg, and archived one at a time.
  4. For notes.txt, path becomes the full path to uploads/notes.txt. File.file? returns true, so Ruby prints the message.
  5. For photo.jpg, the same check succeeds and Ruby prints another message.
  6. For archived, File.file? returns false because it is a directory. next skips 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.

Take Quiz

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

ToolBest useIncludes . and ..?Result yielded
Dir.foreach(folder)Basic iteration over direct entriesYesEntry name
Dir.each_child(folder)Direct entries without special namesNoEntry name
Dir.children(folder)You need an array of direct entry namesNoArray of names
Dir.glob("path/*.csv")Process files matching a filename patternNoMatching paths
Find.find(folder)Recursively visit a directory treeNoPaths, 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.

Take Quiz

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 entry is usually a name, not a complete path.
  • Use File.join to build paths.
  • Dir.foreach yields . and ..; Dir.each_child does 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.

Take Quiz

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.

Take Quiz

Keep learning

Related questions

View more