Working with Tar file archives (Tarballs)

Add these crates to your own project:

cargo add flate2 tar

Compress a directory into tarball

flate2-badge tar-badge cat-compression-badge

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

flate2-badge tar-badge cat-compression-badge

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

flate2-badge tar-badge cat-compression-badge

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(())
}