rubyzip
Rubyzip is a ruby library for reading and writing zip files.
Important notes
Updating to version 3.0
The public API of some classes has been modernized to use named parameters for optional arguments. Please check your usage of the following Rubyzip classes:
File
Entry
InputStream
OutputStream
Please see Updating to version 3.x in the wiki for details.
Requirements
Version 3.x requires at least Ruby 3.0.
Version 2.x requires at least Ruby 2.4, and is known to work on Ruby 3.x.
It is not recommended to use any versions of Rubyzip earlier than 2.3 due to security issues.
Installation
Rubyzip is available on RubyGems:
gem install rubyzip
Or in your Gemfile:
gem 'rubyzip'
Usage
Basic zip archive creation
require 'rubygems'
require 'zip'
folder = "Users/me/Desktop/stuff_to_zip"
input_filenames = ['image.jpg', 'description.txt', 'stats.csv']
zipfile_name = "/Users/me/Desktop/archive.zip"
Zip::File.open(zipfile_name, create: true) do |zipfile|
input_filenames.each do |filename|
# Two arguments:
# - The name of the file as it will appear in the archive
# - The original file, including the path to find it
zipfile.add(filename, File.join(folder, filename))
end
zipfile.get_output_stream("myFile") { |f| f.write "myFile contains just this" }
end
Zipping a directory recursively
Copy from here
require 'zip'
# This is a simple example which uses rubyzip to
# recursively generate a zip file from the contents of
# a specified directory. The directory itself is not
# included in the archive, rather just its contents.
#
# Usage:
# directory_to_zip = "/tmp/input"
# output_file = "/tmp/out.zip"
# zf = ZipFileGenerator.new(directory_to_zip, output_file)
# zf.write()
class ZipFileGenerator
# Initialize with the directory to zip and the location of the output archive.
def initialize(input_dir, output_file)
@input_dir = input_dir
@output_file = output_file
end
# Zip the input directory.
def write
entries = Dir.entries(@input_dir) - %w[. ..]
::Zip::File.open(@output_file, create: true) do |zipfile|
write_entries entries, '', zipfile
end
end
private
# A helper method to make the recursion work.
def write_entries(entries, path, zipfile)
entries.each do |e|
zipfile_path = path == '' ? e : File.join(path, e)
disk_file_path = File.join(@input_dir, zipfile_path)
if File.directory? disk_file_path
recursively_deflate_directory(disk_file_path, zipfile, zipfile_path)
else
put_into_archive(disk_file_path, zipfile, zipfile_path)
end
end
end
def recursively_deflate_directory(disk_file_path, zipfile, zipfile_path)
zipfile.mkdir zipfile_path
subdir = Dir.entries(disk_file_path) - %w[. ..]
write_entries subdir, zipfile_path, zipfile
end
def put_into_archive(disk_file_path, zipfile, zipfile_path)
zipfile.add(zipfile_path, disk_file_path)
end
end
Save zip archive entries sorted by name
To save zip archives with their entries sorted by name (see below), set ::Zip.sort_entries
to true
Vegetable/
Vegetable/bean
Vegetable/carrot
Vegetable/celery
fruit/
fruit/apple
fruit/kiwi
fruit/mango
fruit/orange
Opening an existing zip file with this option set will not change the order of the entries automatically. Altering the zip file - adding an entry, renaming an entry, adding or changing the archive comment, etc - will cause the ordering to be applied when closing the file.
Default permissions of zip archives
On Posix file systems the default file permissions applied to a new archive
are (0666 - umask), which mimics the behavior of standard tools such as touch
.
On Windows the default file permissions are set to 0644 as suggested by the Ruby File documentation.
When modifying a zip archive the file permissions of the archive are preserved.
Reading a Zip file
MAX_SIZE = 1024**2 # 1MiB (but of course you can increase this)
Zip::File.open('foo.zip') do |zip_file|
# Handle entries one by one
zip_file.each do |entry|
puts "Extracting #{entry.name}"
raise 'File too large when extracted' if entry.size > MAX_SIZE
# Extract to file or directory based on name in the archive
entry.extract
# Read into memory
content = entry.get_input_stream.read
end
# Find specific entry
entry = zip_file.glob('*.csv').first
raise 'File too large when extracted' if entry.size > MAX_SIZE
puts entry.get_input_stream.read
end
Notes on Zip::InputStream
Zip::InputStream
can be used for faster reading of zip file content because it does not read the Central directory up front.
There is one exception where it can not work however, and this is if the file does not contain enough information in the local entry headers to extract an entry. This is indicated in an entry by the General Purpose Flag bit 3 being set.
If bit 3 (0x08) of the general-purpose flags field is set, then the CRC-32 and file sizes are not known when the header is written. The fields in the local header are filled with zero, and the CRC-32 and size are appended in a 12-byte structure (optionally preceded by a 4-byte signature) immediately after the compressed data.
If Zip::InputStream
finds such an entry in the zip archive it will raise an exception (Zip::StreamingError
).
Zip::InputStream
is not designed to be used for random access in a zip file. When performing any operations on an entry that you are accessing via Zip::InputStream.get_next_entry
then you should complete any such operations before the next call to get_next_entry
.
zip_stream = Zip::InputStream.new(File.open('file.zip'))
while entry = zip_stream.get_next_entry
# All required operations on `entry` go here.
end
Any attempt to move about in a zip file opened with Zip::InputStream
could result in the incorrect entry being accessed and/or Zlib buffer errors. If you need random access in a zip file, use Zip::File
.
Password Protection (Experimental)
Rubyzip supports reading/writing zip files with traditional zip encryption (a.k.a. "ZipCrypto"). AES encryption is not yet supported. It can be used with buffer streams, e.g.:
Version 2.x
# Writing.
enc = Zip::TraditionalEncrypter.new('password')
buffer = Zip::OutputStream.write_buffer(::StringIO.new(''), enc) do |output|
output.put_next_entry(