我有一个类似于以下所示的目录结构。它深入到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的另一个好处是,如果目录已经存在,它不会引发异常;如果目录已经存在,它似乎也不会覆盖目录(及其中的文件)。
我想到的第二个解决方案是对第一个解决方案的重大改进。但是,还有一种更优雅的方式吗?
答案 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)