298

How do I write a loop in ruby so that I can execute a block of code on each file?

I'm new to ruby, and I've concluded that the way to do this is a do each loop.
The ruby file will be executed from a different directory than the directory I want to loop through.

I've tried the Dir.foreach and I couldn't get it to work.

Nakilon
  • 34,866
  • 14
  • 107
  • 142
Andrew
  • 2,981
  • 2
  • 16
  • 3
  • 2
    Can you specify what happened when you tried to make it work? What exact code did you try (or the relevant chunk, if it's long)? What error messages did you get? `Dir.foreach` works to iterate over the contents of a directory, so something else much be going on. – Telemachus Mar 25 '10 at 01:05
  • 4
    If you only want files within your directory, don't forget to test for files when iterating over the directory contents: `do_something_with(entry) if File.file?(entry)` – glenn jackman Mar 25 '10 at 11:58
  • 3
    Use `'img/*.{jpg,png,gif,jpeg}'` to grab multiple extensions. – Benjamin Crouzier Mar 05 '13 at 16:59
  • @ChrisPeters seems unlikely unfortunately, as the OP hasn't been on the site in over four years. – Joe Kennedy Jul 30 '14 at 04:59

8 Answers8

463

As others have said, Dir::foreach is a good option here. However, note that Dir::foreach and Dir::entries will always include . and .. (the current and parent directories). You will generally not want to work on them, so you can use Dir::each_child or Dir::children (as suggested by ma11hew28) or do something like this:

Dir.foreach('/path/to/dir') do |filename|
  next if filename == '.' or filename == '..'
  # Do work on the remaining files & directories
end

Dir::foreach and Dir::entries (as well as Dir::each_child and Dir::children) also include hidden files & directories. Often this is what you want, but if it isn't, you need to do something to skip over them.

Alternatively, you might want to look into Dir::glob which provides simple wildcard matching:

Dir.glob('/path/to/dir/*.rb') do |rb_filename|
  # Do work on files & directories ending in .rb
end
ma11hew28
  • 121,420
  • 116
  • 450
  • 651
Telemachus
  • 19,459
  • 7
  • 57
  • 79
  • 13
    Use `Dir.foreach` if the directory contains a huge number of files! – Tilo Aug 29 '13 at 20:38
  • 6
    thanks! Small mod to make it even better: `next if File.directory? item` – mr.buttons May 13 '15 at 01:10
  • @mr.buttons That won't always do the right thing. Sometimes people want to work on *directories* as well as files. I gave code for avoiding the special listings for `.` or `..` because people almost always want to ignore those two. – Telemachus May 15 '15 at 22:53
  • 3
    @Tilo: just out of interest, care to explain in a bit more detail why? :) – mkataja Jun 10 '15 at 11:04
  • 11
    @mkataja `Dir.foreach` iterates rather than builds up a (potentially huge) array up front (which `Dir.glob` does). So if the directory is really huge, it can make a performance difference. Under normal circumstances you wouldn't notice, but under stress conditions, it can absolutely matter. – Telemachus Jun 11 '15 at 19:11
102

This is my favorite method for being easy to read:

Dir.glob("*/*.txt") do |my_text_file|
  puts "working on: #{my_text_file}..."
end

And you can even extend this to work on all files in subdirs:

Dir.glob("**/*.txt") do |my_text_file| # note one extra "*"
  puts "working on: #{my_text_file}..."
end
yckart
  • 32,460
  • 9
  • 122
  • 129
SimplGy
  • 20,079
  • 15
  • 107
  • 144
33

Dir has also shorter syntax to get an array of all files from directory:

Dir['dir/to/files/*'].each do |fname|
    # do something with fname
end
wawka
  • 4,828
  • 3
  • 28
  • 22
  • 1
    What in this code prevents directories from also being used with `fname`'s iterations? – kayleeFrye_onDeck May 30 '17 at 23:22
  • 1
    For future googlers, and to answer @kayleeFrye_onDeck's question, nothing in the code so far limits `fname` to files only. Within the block itself, you can add any limitations you need through tests. – Telemachus Jul 16 '22 at 16:18
26
Dir.foreach("/home/mydir") do |fname|
  puts fname
end
Fred
  • 8,582
  • 1
  • 21
  • 27
14

The find library is designed for this task specifically: https://ruby-doc.org/stdlib-2.5.1/libdoc/find/rdoc/Find.html

require 'find'
Find.find(path) do |file|
  # process
end

This is a standard ruby library, so it should be available

Felix
  • 4,510
  • 2
  • 31
  • 46
Faisal
  • 19,358
  • 4
  • 30
  • 33
  • 1
    `File.find` goes recursively down as far as it can, starting from whatever path you give it. I'm not sure that is what the OP wants. – Telemachus Mar 25 '10 at 16:33
  • I dont seem to have access to that method - Find.find ? Do I need to download a library ot include this functionality ? – blue-sky Apr 01 '11 at 22:35
  • @user470184: "Find" is a standard ruby library and should be available with the default ruby installation. However, you need to "require 'find'" before you can use it. – Faisal Apr 02 '11 at 01:28
  • 1
    @Faisal Can I pass glob patterns like `*.rb` to `find()` – Ashhar Hasan Feb 19 '16 at 20:57
10

To skip . & .., you can use Dir::each_child.

Dir.each_child('/path/to/dir') do |filename|
  puts filename
end

Dir::children returns an array of the filenames.

ma11hew28
  • 121,420
  • 116
  • 450
  • 651
8

I like this one, that hasn't been mentioned above.

require 'pathname'

Pathname.new('/my/dir').children.each do |path|
    puts path
end

The benefit is that you get a Pathname object instead of a string, that you can do useful stuff with and traverse further.

skagedal
  • 2,323
  • 23
  • 34
3
Dir.new('/my/dir').each do |name|
  ...
end
Nick Moore
  • 15,547
  • 6
  • 61
  • 83
  • 1
    In addition to Dir.new('/my/dir') there's also Dir.entries('/my/dir') but Dir.foreach() is a bit more succinct. – the Tin Man Mar 25 '10 at 00:53
  • 5
    @Z.E.D. Also `Dir.foreach` iterates while `Dir.entries` builds the whole array up at once. So if the directory is immense, it's less of a memory hit. (Not usually a big deal, probably, but still...) – Telemachus Mar 25 '10 at 01:02