2009-08-17 106 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