Ruby 创建递归目录的优雅方法

Ruby 创建递归目录的优雅方法,ruby,Ruby,我有一个类似于下面所示的目录结构。它深入到6个级别,在某些情况下,需要在同一级别创建两个目录。我想递归地创建这些目录 pennsylvania bucks medicine pro bio a b c mental a b c physical a b

我有一个类似于下面所示的目录结构。它深入到6个级别,在某些情况下,需要在同一级别创建两个目录。我想递归地创建这些目录

pennsylvania
  bucks
    medicine
      pro
        bio
          a
          b
          c
        mental
          a
          b
          c
        physical 
          a
          b
          c
      non_pro
        bio
          a
          b
          c
        mental
          a
          b
          c
        physical 
          a
          b
          c
一种丑陋且可能有缺陷的方法是这样的:

unless File.exists?("pennsylvania")
  Dir.mkdir "pennsylvania"
end

unless File.exists?("pennsylvania/bucks")
  Dir.mkdir "pennsylvania/bucks"
end

unless File.exists?("pennsylvania/bucks/medicine")
  Dir.mkdir "pennsylvania/bucks/medicine"
end

unless File.exists?("pennsylvania/bucks/medicine/pro")
  Dir.mkdir "pennsylvania/bucks/medicine/pro"
end
等等。当我们沿着需要创建的目录结构向下导航时,您可以看到这是多么低效。我正在寻找一个更优雅的解决方案。大概是这样的:

unless File.exists?("pennsylvania")
  Dir.mkdir "pennsylvania"
end

unless File.exists?("pennsylvania/bucks")
  Dir.mkdir "pennsylvania/bucks"
end

unless File.exists?("pennsylvania/bucks/medicine")
  Dir.mkdir "pennsylvania/bucks/medicine"
end

unless File.exists?("pennsylvania/bucks/medicine/pro")
  Dir.mkdir "pennsylvania/bucks/medicine/pro"
end
使用FileUtils类(虽然不是标准库的一部分)会使它更好:

['pro', 'non_pro'].each do |lev1|
  ['bio', 'mental', 'physical'].each do |lev2|
    ['a', 'b', 'c'].each do |lev3|
      FileUtils.mkdir_p "pennsylvania/bucks/medicine/#{lev1}/#{lev2}/#{lev3}"
    end
  end
end
FileUtils的另一个好处是,如果目录已经存在,它不会引发异常;如果目录已经存在,它似乎不会覆盖目录(以及其中的文件)

我提出的第二个解决方案是对第一个解决方案的重大改进。但是还有更优雅的方式吗?

是一个不错的选择,它是标准库的一部分

您可以使用生成树

levels = ['pro', 'non_pro'].product(
  ['bio', 'mental', 'physical'],
  ['a', 'b', 'c']
)
然后,您可以使您的块与任何数量的级别一起工作


请注意,尽管这非常优雅,但它并不是最有效的方法。问题是每次调用
FileUtils.mkdir\u p
都会尝试创建每个子目录,如果它得到错误,请检查它是否已经存在。对于快速文件系统上的少量目录,这很好。但对于大型树或速度较慢的文件系统(如网络文件系统),这可能会影响性能

为了更有效地使用文件系统,您应该执行类似于这种递归的操作

levels = [
  ['pennsylvania'],
  ['bucks'],
  ['medicine'],
  ['pro', 'non_pro'],
  ['bio', 'mental', 'physical'],
  ['a', 'b', 'c']
]

def make_subdirs(levels, base = [])
  return if levels.empty?

  levels[0].each { |dir|
    new_base = [*base, dir]
    mkdir_ignore_if_exists(new_base)
    make_subdirs(levels[1..-1], new_base)
  }
end

private def mkdir_ignore_if_exists(dirs)
  Dir.mkdir(dirs.join("/"))
rescue Errno::EEXIST
end

make_subdirs(levels)

有时,优雅比效率更好,如果它能增强可读性。@Donato优雅的代码在运行五分钟后就不优雅了。性能所需的复杂性可以封装,正如我在上面所做的那样。就像我说的,这只是一个缓慢文件系统(例如SSHFS)或真正巨大的目录树(在这一点上你应该考虑数据库)的问题。有一种更优雅的方法吗?使用数据库?