tags:

views:

781

answers:

4

I'm attempting to unzip a file with several files that may or may not already exist in the target directory. It seems the default behavior is to throw an exception if the file already exists.

How do I unzip to a directory and simply overwrite existing files?

Here's my code:

begin
  Zip::ZipFile.open(source) do |zipfile|
    dir = zipfile.dir
    dir.entries('.').each do |entry|
      zipfile.extract(entry, "#{target}/#{entry}")
    end
  end
rescue Exception => e
  log_error("Error unzipping file: #{local_zip}  #{e.to_s}")
end
A: 

Edit: Modified code to remove target file if it exists beforehand.

require 'rubygems'
require 'fileutils'
require 'zip/zip'

def unzip_file(file, destination)
  Zip::ZipFile.open(file) { |zip_file|
   zip_file.each { |f|
     f_path=File.join(destination, f.name)
     if File.exist?(f_path) then
       FileUtils.rm_rf f_path
     end
     FileUtils.mkdir_p(File.dirname(f_path))
     zip_file.extract(f, f_path)
   }
  }
end

unzip_file('/path/to/file.zip', '/unzip/target/dir')

Edit: Modified code to remove target directory if it exists beforehand.

require 'rubygems'
require 'fileutils'
require 'zip/zip'

def unzip_file(file, destination)
  if File.exist?(destination) then
    FileUtils.rm_rf destination
  end
  Zip::ZipFile.open(file) { |zip_file|
   zip_file.each { |f|
     f_path=File.join(destination, f.name)
     FileUtils.mkdir_p(File.dirname(f_path))
     zip_file.extract(f, f_path)
   }
  }
end

unzip_file('/path/to/file.zip', '/unzip/target/dir')

Here's the original code from Mark Needham:

require 'rubygems'
require 'fileutils'
require 'zip/zip'

def unzip_file(file, destination)
  Zip::ZipFile.open(file) { |zip_file|
   zip_file.each { |f|
     f_path=File.join(destination, f.name)
     FileUtils.mkdir_p(File.dirname(f_path))
     zip_file.extract(f, f_path) unless File.exist?(f_path)
   }
  }
end

unzip_file('/path/to/file.zip', '/unzip/target/dir')
Adam Bernier
Thanks for the answer but it looks like this won't overwrite an existing file. It will just skip it if it exists.
digitalsanctum
...indeed it does skip files that exist. How silly of me to not test that particular use-case before posting. My apologies. Please see my edited version which will remove the destination directory if it exists beforehand.
Adam Bernier
And my second solution was also suboptimal. Because deleting the entire directory is probably rarely advisable; but I believe the third time is a charm: I added a bit of code to delete the file if it exists before writing the new file.
Adam Bernier
+1  A: 

It appears that extract() takes an optional block (onExistsProc) that allows you to determine what to do with the file if it already exists - return true to overwrite, false to raise an exception.

If you wanted to simply overwrite all existing files, you could do:

zipfile.extract(entry, "#{target}/#{entry}") { true }

If you want to do some more complex logic to handle specific entries differently, you can do:

zipfile.extract(entry, "#{target}/#{entry}") {|entry, path| some_logic(entry, path) }

EDIT: fixed answer - as pointed out by Ingmar Hamer, my original answer passed the block as a parameter when it's expected using the above syntax.

Greg Campbell
This answer does not actually work as posted. See the answer posted by Ingmar Hamer and give him a point for his correction.
Stever B
+4  A: 

Just to save others the trouble:

The extract command in answer 2 is incorrect:

The third (proc) parameter is specified wtih an ampersand, meaning ruby expects it to be in {}-Brackets after the method call like this:

zipfile.extract(entry, "#{target}/#{entry}"){ true }

or (if you need more complex logic)

zipfile.extract(entry, "#{target}/#{entry}") {|entry, path| some_logic(entry, path) }

If you use the example given in Post #2 you'll get a "invalid arguments (3 for 2)" error...

Ingmar Hamer
Thank you. I'm new to Ruby and beat my head against this particular wall for an hour.
Stever B
A: 

This link here provides a good example which I have verified works. Just needs to have a require 'fileutils' added to it.

yonkeltron