创建递归目录的优雅方法

时间:2018-12-06 23:10:38

标签: ruby

我有一个类似于以下所示的目录结构。它深入到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

以此类推。您可以看到当我们导航到需要创建的目录结构时,效率如何降低。我正在寻找一个更优雅的解决方案。像这样:

使用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的另一个好处是,如果目录已经存在,它不会引发异常;如果目录已经存在,它似乎也不会覆盖目录(及其中的文件)。

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

1 个答案:

答案 0 :(得分:2)

FileUtils.mkdir_p是一个不错的选择,它是标准库的一部分。

您可以使用Array#product来生成树。

levels = ['pro', 'non_pro'].product(
  ['bio', 'mental', 'physical'],
  ['a', 'b', 'c']
)

然后,您可以使用Array#join使您的块在任意数量的级别上工作。

base = ["pennsylvania","bucks","medicine"]
levels.each do |level|
  FileUtils.mkdir_p( (base + level).join("/") )
end

请注意,尽管这非常优雅,但这并不是最有效的方法。问题在于,每次对FileUtils.mkdir_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)