Working with Tar file archives (Tarballs)
Add these crates to your own project:
cargo add flate2 tar
Compress a directory into tarball
Create some test files, and then compress those into a tar archive file named archive.tar.gz
.
In the example, first some test files are created in two separate directories.
For the actual tar archive, the first step is to create a new File
,
which is then wrapped in GzEncoder
and then in tar::Builder
. After that,
the test data directories (and recursively the files within them) are added
to the tar archive file via the Builder::append_dir_all
function.
The GzEncoder
is responsible for transparently compressing the
data prior to writing it into archive.tar.gz
.
use std::error::Error; use std::fs::{create_dir_all, File}; use std::io::Write; use std::path::Path; use flate2::Compression; use flate2::write::GzEncoder; // Write contents of string into a file fn echo(contents: &str, path_name: &str) -> Result<(), Box<dyn Error>> { let file_path = Path::new(path_name); let mut file_ref = File::create(file_path)?; file_ref.write_all(contents.as_bytes())?; Ok(()) } // create some files, and then archive them fn main() -> Result<(), Box<dyn Error>> { // make two directories, it is OK if they already exist create_dir_all("temp1")?; create_dir_all("temp7")?; // make four files echo("first\n", "temp1/one.txt")?; echo("second\n", "temp1/two.txt")?; echo("third\n", "temp7/three.txt")?; echo("fourth\n", "temp7/four.txt")?; // create archive file using gzip compression let file_ref = File::create("archive.tar.gz")?; let encoder = GzEncoder::new(file_ref, Compression::default()); let mut archive = tar::Builder::new(encoder); // Add directories and files to archive, renaming one of them inside // the archive itself. archive.append_dir_all("temp1-renamed", "temp1")?; archive.append_dir_all("temp7", "temp7")?; println!("successfully created archive"); Ok(()) }
As with other examples, the error handling is very simplified and uses
Result<(), Box<dyn Error>>
as the return type for both the function
echo()
as well as main()
itself. See the [Error
] page for better
approaches to error handling in applications and libraries.
Since this creates files on the filesystem, it cannot be run on the Rust
Playground. You can either clone the Rust Cookbook project, or just
create a new Cargo project and replace the main.rs
with the contents above.
Run this example from the Rust Cookbook project:
cargo run --example tar-compress
To examine the result, the tar
command line utility can be used. It is
installed by default on most Linux systems and MacOS. tar
is also
available inside WSL on Windows.
Examine the newly created archive via:
tar xvf archive.tar.gz
The output will be similar to this:
drwxrwsr-x 1000/1000 0 2022-02-20 11:22 temp1-renamed/
-rw-rw-r-- 1000/1000 7 2022-02-20 11:22 temp1-renamed/two.txt
-rw-rw-r-- 1000/1000 6 2022-02-20 11:22 temp1-renamed/one.txt
drwxrwsr-x 1000/1000 0 2022-02-20 11:22 temp7/
-rw-rw-r-- 1000/1000 7 2022-02-20 11:22 temp7/four.txt
-rw-rw-r-- 1000/1000 6 2022-02-20 11:22 temp7/three.txt
Decompress a tarball
Decompress (using GzDecoder
) and
extract (Archive::unpack
) all files from a compressed tar archive file
named archive.tar.gz
located in the current working directory
to the same location.
use std::fs::File; use flate2::read::GzDecoder; use tar::Archive; fn main() -> Result<(), std::io::Error> { let path = "archive.tar.gz"; // archive file in current working directory (CWD) let tar_gz = File::open(path)?; let tar = GzDecoder::new(tar_gz); let mut archive = Archive::new(tar); archive.unpack(".")?; // unpack to CWD println!("file extraction success"); Ok(()) }
Since these examples interact with the filesystem, they can't be run on the Rust Playground. Either clone the Rust Cookbook repository or copy and paste the example code into a newly created project.
Run the tar compression example above to create a suitable archive.tar.gz
file. Then remove the temporary files and directories if the
tar-compress.rs
with:
rm -r temp1 temp7
If the Rust Cookbook repository has been cloned, run:
cargo run --example tar-compress
If using the previously generated example, the temp1-renamed
and temp7
directories
and files have been created.
Decompress a tarball while removing a prefix from the paths
Iterate over the Archive::entries
. Use Path::strip_prefix
to
remove the specified path prefix (bundle/logs
). Finally, extract
the tar::Entry
via Entry::unpack
.
use std::error::Error; use std::fs::File; use std::path::PathBuf; use flate2::read::GzDecoder; use tar::Archive; fn main() -> Result<(), Box<dyn Error>> { let file = File::open("archive.tar.gz")?; let mut archive = Archive::new(GzDecoder::new(file)); let prefix = "bundle/logs"; println!("Extracted the following files:"); archive .entries()? .filter_map(|e| e.ok()) .map(|mut entry| -> Result<PathBuf, Box<dyn Error>> { let path = entry.path()?.strip_prefix(prefix)?.to_owned(); entry.unpack(&path)?; Ok(path) }) .filter_map(|e| e.ok()) .for_each(|x| println!("> {}", x.display())); Ok(()) }