tags:

views:

566

answers:

5

I'm trying to write some ruby that would recursively search a given directory for all empty child directories and remove them.

Thoughts?

Note: I'd like a script version if possible. This is both a practical need and a something to help me learn.

+2  A: 

Why not just use shell?

find . -type d -empty -exec rmdir '{}' \;

Does exactly what you want.

koenigdmj
I was hoping for a ruby script version to help grasp working with files. I could invoke the above with 'sh <your cmd>' from a rake script I suppose. Got a script version?
TheDeeno
The script command would be exactly the same. However, the below post has an answer that will suit you.
koenigdmj
+3  A: 

In ruby:

Dir['**/*']                                            \
  .select { |d| File.directory? d }                    \
  .select { |d| (Dir.entries(d) - %w[ . .. ]).empty? } \
  .each   { |d| Dir.rmdir d }
kch
could you explain the "-" in line 3?
TheDeeno
I'm subtracting the array containing the strings "." and ".." from the array of directory entries, since every directory contains those two special entries.
kch
also, could you explain what are the "\"s are for? line continuation? are they needed?
TheDeeno
They're line continuation, yes. They're not needed if you put the dot at the end of the previous line, but I like them at the beginning to make it clear that this line is continuing from the previous. (Although really, I just formatted as such for SO display purposes. I generally leave these things in a single line, up to ~160 chars or so. Some people hate me, yes. I think they're using TTYs.)
kch
haha. Thanks kch you saved me some time. I had all the doco pulled up, but was missing part of the bigger picture to put it all together.
TheDeeno
You see, the way I wrote the code, if I dropped the \s, ruby could interpret the line as a complete expression, and so it would.
kch
A: 
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
xyz
Rescuing from any exception willy-nilly is not the best idea in real production code.
kch
Usually, I'd have to agree, but this is sort of a borderline situation. In any case, I updated the rescue statement.
xyz
Yea, it's one of these things that's fine as long as you know what you're doing. But one googler hitting this page might be looking into it just for a quick script, or for a library that'll have a major role in a filesystem intensive application. So, basically, n00bs get the safe code by default, and people who supposedly know what they're doing will do so at their own risk.
kch
A: 

I've tested this script on OS X, but if you are on Windows, you'll need to make changes.

You can find the files in a directory, including hidden files, with Dir#entries.

This code will delete directories which become empty once you've deleted any sub-directories.

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
alltom
+1  A: 
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
dadooda