2009-08-17 50 views
8

我想写一些ruby,递归地搜索给定目录中的所有空子目录并将其删除。Ruby:我如何递归查找和删除空目录?

想法?

注:如果可能,我想要一个脚本版本。这既是一种实际需要,也是一种帮助我学习的东西。

+1

首先想到的是:系统“find。-type d | xargs rmdir -p 2>/dev/null” – kch 2009-08-17 21:53:27

+0

只需要注意,我不想在命令行中一次性执行此操作。它将成为一个红宝石脚本。你上面的是一个cmd行版本no? – 2009-08-17 21:55:07

+0

好吧,这是一个shell命令,是的,但是使用'Kernel.system'从ruby中调用;) – kch 2009-08-17 22:02:25

回答

15

在红宝石:

Dir['**/*']           \ 
    .select { |d| File.directory? d }     \ 
    .select { |d| (Dir.entries(d) - %w[ . .. ]).empty? } \ 
    .each { |d| Dir.rmdir d } 
+0

你能解释第3行中的“ - ”吗? – 2009-08-17 22:03:37

+0

我正在减去包含字符串的数组“。”和“..”,因为每个目录都包含这两个特殊条目。 – kch 2009-08-17 22:03:48

+0

另外,你能解释一下“\”是什么吗?续行?他们需要吗? – 2009-08-17 22:05:21

2

为什么不使用shell?

找到。 -type d -empty -exec rmdir'{}'\;

完全是你想要的。

+0

我希望得到一个ruby脚本版本来帮助你理解文件的工作。我可以从一个rake脚本中用'sh '调用上述内容。有一个脚本版本? – 2009-08-17 21:58:40

+0

脚本命令将完全相同。不过,下面的帖子有一个适合你的答案。 – koenigdmj 2009-08-17 22:10:00

0
Dir.glob('**/*').each do |dir| 
    begin 
    Dir.rmdir dir if File.directory?(dir) 
    # rescue # this can be dangereous unless used cautiously 
    rescue Errno::ENOTEMPTY 
    end 
end 
+0

从任何异常中抢救并不是真正的生产代码中最好的主意。 – kch 2009-08-17 22:19:15

+0

通常,我不得不同意,但这是一种边缘情况。无论如何,我更新了救援声明。 – xyz 2009-08-17 22:35:45

+0

是的,只要你知道你在做什么,这就是其中之一。但是有一位Google员工正在浏览这个页面,可能只是为了一个快速的脚本或者一个在文件系统密集型应用程序中起主要作用的库。所以,基本上,n00bs默认会得到安全的代码,而那些据说知道自己在做什么的人会冒这样的风险。 – kch 2009-08-21 03:31:09

0

我测试过的OS X这个脚本,但如果你使用的是Windows,你需要做出改变。

您可以使用Dir#条目找到包含隐藏文件的目录中的文件。

此代码将删除删除任何子目录后变为空的目录。

def entries(dir) 
    Dir.entries(dir) - [".", ".."] 
end 

def recursively_delete_empty(dir) 
    subdirs = entries(dir).map { |f| File.join(dir, f) }.select { |f| File.directory? f } 
    subdirs.each do |subdir| 
    recursively_delete_empty subdir 
    end 

    if entries(dir).empty? 
    puts "deleting #{dir}" 
    Dir.rmdir dir 
    end 
end 
1
module MyExtensions 
    module FileUtils 
    # Gracefully delete dirs that are empty (or contain empty children). 
    def rmdir_empty(*dirs) 
     dirs.each do |dir| 
     begin 
      ndel = Dir.glob("#{dir}/**/", File::FNM_DOTMATCH).count do |d| 
      begin; Dir.rmdir d; rescue SystemCallError; end 
      end 
     end while ndel > 0 
     end 
    end 
    end 

    module ::FileUtils 
    extend FileUtils 
    end 
end 
2
Dir['/Users/path/Movies/incompleteAnime/foom/**/*']. \ 
select { |d| File.directory? d }. \ 
sort.reverse. \ 
each {|d| Dir.rmdir(d) if Dir.entries(d).size == 2} 

就像第一个例子,但第一个例子似乎并没有处理递归位。排序和反向确保我们首先处理最嵌套的目录。

我想sort.reverse可以写成sort {|a,b| b <=> a}效率

3

你必须按相反的顺序进行删除,否则如果你有一个子目录吧,你会删除栏空目录foo的,但不是foo。

Dir.glob(dir + "/**/*").select { |d| 
    File.directory?(d) 
    }.reverse_each { |d| 
    if ((Dir.entries(d) - %w[ . .. ]).empty?) 
     Dir.rmdir(d) 
    end 
    } 
5

查看来自kch,dB的例子。毗湿奴及以上,我已经把一个班轮,我认为是一个更优雅的解决方案:

Dir['**/'].reverse_each { |d| Dir.rmdir d if Dir.entries(d).size == 2 } 

我用'**/',而不是'/**/*'的水珠,它仅返回目录,所以我没有以后再测试它是否是一个目录。根据post,我使用的是reverse_each而不是sort.reverse.each,因为它更短并且效率更高。我更喜欢​​到(Dir.entries(d) - %w[ . .. ]).empty?,因为它更容易阅读和理解,但如果必须在Windows上运行脚本,(Dir.entries(d) - %w[ . .. ]).empty?可能会更好。

我已经在Mac OS X上对此进行了相当多的测试,即使使用递归空目录,它也能正常工作。

+0

一个非常优雅的解决方案汇编。 – Shadow 2014-04-22 05:43:40

+1

尼斯重构,但我想提及。并且..比size == 2更直观,所以我将使用'Dir ['lib/** /']。reverse_each {| d | Dir.rmdir d if Dir.entries(d).sort ==%w(.. ..)} – mahemoff 2015-02-22 19:21:50

+0

是的,那种更直观。 – Adam 2015-02-24 06:35:11