36

I want to unzip a lot of zip files. Is there a module or script that checks which format the zip file is and decompresses it? This should work on Linux, I don't care about other OSs.

Adi Inbar
  • 12,097
  • 13
  • 56
  • 69
gustavgans
  • 5,141
  • 13
  • 41
  • 51
  • `tar cz` (usually producing `*.tgz` or `.tar.gz` files) use _gzip_ compression. `gzip` is incompatible with (and this question has nothing to do with) _zip_ / _unzip_. – hagello Nov 22 '22 at 06:58

4 Answers4

37

To extract files from a .tar.gz file you can use the following methods from packages distributed with Ruby:

require 'rubygems/package'
require 'zlib'
tar_extract = Gem::Package::TarReader.new(Zlib::GzipReader.open('Path/To/myfile.tar.gz'))
tar_extract.rewind # The extract has to be rewinded after every iteration
tar_extract.each do |entry|
  puts entry.full_name
  puts entry.directory?
  puts entry.file?
  # puts entry.read
end
tar_extract.close

Each entry of type Gem::Package::TarReader::Entry points to a file or directory within the .tar.gz file.

Similar code can be used (replace Reader with Writer) to write files to a .tar.gz file.

juco
  • 6,331
  • 3
  • 25
  • 42
Florian Feldhaus
  • 5,567
  • 2
  • 38
  • 46
  • 1
    To expand on this great answer, you can extract files to disk with `Gem::Package.new("").extract_tar_gz(io, destination)`, where `io` is an object like `File.open("file.tar.gz", "rb")` – Andrew Kane Apr 29 '20 at 06:44
  • @AndrewKane worked perfectly, though passing '' to the package kinda weird. How does it suppose to work? – lilkunien Jan 29 '22 at 14:55
32

The easiest way is to probably use Zlib

Zlib is a Ruby library. What follows is a simple program to grab a Zipped file from a particular URL, unzip it, and paste its contents to the screen.

require 'zlib' 
require 'open-uri'

uri = "www.somedomain.com/filename.gz"
source = open(uri)
gz = Zlib::GzipReader.new(source) 
result = gz.read
puts result

I hope this helps.

Community
  • 1
  • 1
  • Mavryx: When I am trying your snippet I am getting the following error: unzip.rb:5: undefined local variable or method `uri' for # (NameError) – maetthew Feb 24 '11 at 00:36
  • @maetthew check line 4 and make sure that you use same variable (uri here) in both lines – BurmajaM Nov 13 '12 at 13:02
16

Although Florian's answer is right, it does not take into account tar LongLinks (Try extracting jdk-7u40-linux-i586.tar.gz from oracle :P ). The following code should be able to do this:

require 'rubygems/package'
require 'zlib'

TAR_LONGLINK = '././@LongLink'
tar_gz_archive = '/path/to/archive.tar.gz'
destination = '/where/extract/to'

Gem::Package::TarReader.new( Zlib::GzipReader.open tar_gz_archive ) do |tar|
  dest = nil
  tar.each do |entry|
    if entry.full_name == TAR_LONGLINK
      dest = File.join destination, entry.read.strip
      next
    end
    dest ||= File.join destination, entry.full_name
    if entry.directory?
      File.delete dest if File.file? dest
      FileUtils.mkdir_p dest, :mode => entry.header.mode, :verbose => false
    elsif entry.file?
      FileUtils.rm_rf dest if File.directory? dest
      File.open dest, "wb" do |f|
        f.print entry.read
      end
      FileUtils.chmod entry.header.mode, dest, :verbose => false
    elsif entry.header.typeflag == '2' #Symlink!
      File.symlink entry.header.linkname, dest
    end
    dest = nil
  end
end
Community
  • 1
  • 1
Draco Ater
  • 20,820
  • 8
  • 62
  • 86
2

Draco, thx for you snippet. Some TARs encode directories as paths ending with '/' - see this Wiki. Examlple tar is Oracle Server JRE 7u80 for Windows. This will work for them:

require 'fileutils'
require 'rubygems/package'
require 'zlib'

TAR_LONGLINK = '././@LongLink'

Gem::Package::TarReader.new( Zlib::GzipReader.open tar_gz_archive ) do |tar|
        dest = nil
        tar.each do |entry|
            if entry.full_name == TAR_LONGLINK
                dest = File.join destination, entry.read.strip
                next
            end
            dest ||= File.join destination, entry.full_name
            if entry.directory? || (entry.header.typeflag == '' && entry.full_name.end_with?('/'))
                File.delete dest if File.file? dest
                FileUtils.mkdir_p dest, :mode => entry.header.mode, :verbose => false
            elsif entry.file? || (entry.header.typeflag == '' && !entry.full_name.end_with?('/'))
                FileUtils.rm_rf dest if File.directory? dest
                File.open dest, "wb" do |f|
                    f.print entry.read
                end
                FileUtils.chmod entry.header.mode, dest, :verbose => false
            elsif entry.header.typeflag == '2' #Symlink!
                File.symlink entry.header.linkname, dest
            else
                puts "Unkown tar entry: #{entry.full_name} type: #{entry.header.typeflag}."
            end
            dest = nil
        end
    end
end
Community
  • 1
  • 1
TomCZ
  • 354
  • 4
  • 5