Python の os.walk もどきを Ruby で

Python に os.walk という関数があって、よく Python の入門書で自慢されているので、遊びで Ruby でマネしてみました。あるディレクトリ以下を再帰的にトラバースします。ただし、Python の実装そのままではなくて、面倒なのでマネだけにしてあります。同じ機能を実装することもむずかしくないでしょうが、Dir::getwdDir::pwdでカレントディレクトリが取得できるので、意味がないと思います。

Python だとこんな感じ。隠しファイルはスキップしてファイル名表示というコードです。

import os

for root, dirs, files in os.walk("./"):
    for fname in files:
        if fname[0] != ".":
            print(fname)

Ruby だとこんな実装。上と同じ動作ではありません。再帰を使ってブロックと組み合わせてあります。ブロックの中では当然カレントディレクトリも移動しています。.rb の拡張子をもつファイル名を表示するというコードです。

class Dir
  def self.walk1(dir="./", &bk)
    Dir.chdir(dir)
    d = Dir.glob("*").sort
    d.each do |fname|
      if File.directory?(fname)
        Dir.walk1(fname, &bk)
        Dir.chdir("..")
      else
        yield(fname, d)
      end
    end
  end
end

if __FILE__ == $0
  Dir.walk1 do |fname|
    next unless /\.rb$/.match(fname)
    puts "#{Dir::getwd} : #{fname}"
  end
end

結構いい実装に思えるのですけれども、どうでしょう。

もう少し Python の os.walk に近づけた実装。

class Dir
  def self.walk2(dir="./", depth = 0, &bk)
    Dir.chdir(dir)
    files = []
    Dir.glob("*").sort.each do |fname|
      if File.directory?(fname)
        Dir.walk2(fname, depth + 1, &bk)
        Dir.chdir("..")
      else
        files << fname
      end
    end
    yield(Dir::pwd, files, depth)
  end
end

if __FILE__ == $0
  Dir.walk2 do |dir, files|
    files.each {|fname| puts "#{dir} : #{fname}" if /\.rb$/.match(fname)}
  end
end

こちらの方が汎用的かな。

なお、Ruby には標準添付ライブラリに Find というモジュールがあるので、それを使えば簡単です。ただこれ、与えられるファイル名が開始ディレクトリ以下のすべてのパスになってしまうのですね(./**/test.rb とかいう感じ)。ちょっと使いにくい気もします。
library find (Ruby 2.2.0)

require 'find'

Find.find("./") do |fname|
  if File.file?(fname) and /\.rb$/.match(fname)
    puts File.basename(fname)
  end
end