ffi-libarchive
| Language | Ruby |
|---|---|
| License | Apache 2.0 |
| Repository | github.com/chef/ffi-libarchive |
| Documentation | README |
| Depends on | the ffi gem and the libarchive shared library |
| Read / write | yes / yes; what libarchive supports for 7z (LZMA, LZMA2, PPMd, BZip2, Deflate, BCJ/BCJ2); no encryption |
ffi-libarchive, maintained by Chef, binds the C libarchive library through Ruby FFI, so it needs no compiler, only libarchive installed. Archive::Reader iterates over the entries of an archive and extracts or reads them; Archive::Writer creates archives. The same API handles 7z, TAR (gzip, bzip2, xz, zstd), ZIP, CPIO, ISO and others, with the format detected automatically when reading.
libarchive can’t decrypt or create encrypted 7z archives; use seven-zip for those.
Installation#
Shell
# the C library
sudo apt install libarchive13 # Debian / Ubuntu
brew install libarchive # macOS
# the gem
gem install ffi-libarchive
Basic usage: list and extract a 7z archive#
Ruby
require "ffi-libarchive"
Dir.mkdir("output") unless Dir.exist?("output")
Archive.read_open_filename("archive.7z") do |reader|
reader.each_entry do |entry|
puts "#{entry.pathname} #{entry.size}"
reader.extract(entry, Archive::EXTRACT_PERM | Archive::EXTRACT_TIME, destination: "output")
end
end
How to read a file from a 7z archive into memory#
read_data returns the data of the current entry; call it while iterating:
Ruby
require "ffi-libarchive"
content = nil
Archive.read_open_filename("archive.7z") do |reader|
reader.each_entry do |entry|
next unless entry.pathname == "notes/readme.txt"
content = reader.read_data
break
end
end
puts content
How to create a 7z archive#
Ruby
require "ffi-libarchive"
Archive.write_open_filename("backup.7z", Archive::COMPRESSION_NONE, Archive::FORMAT_7ZIP) do |ar|
["report.pdf", "notes.txt"].each do |path|
data = File.binread(path)
ar.new_entry do |e|
e.pathname = path
e.size = data.bytesize
e.filetype = Archive::Entry::FILE
e.perm = 0o644
e.mtime = File.mtime(path)
ar.write_header(e)
ar.write_data(data)
end
end
end
The 7z format applies its own compression (LZMA2 by default), so the outer compression filter is COMPRESSION_NONE. All entries go into one solid block.
Tips#
Archive::Reader.open_filename(path)without a block returns a reader you mustcloseyourself.- Use
strip_components:(Archive::Reader.open_filename(path, strip_components: 1)) to drop the top-level folder when extracting. - 7z needs a seekable file: read from a file or from memory with
Archive.read_open_memory(string), not from a pipe.