Skip to content
Python Basics: Read Tar Members Without Extracting Files

Python Basics: Read Tar Members Without Extracting Files

October 23, 2026

An archive inspection script can list metadata or read a selected member without unpacking paths onto disk. tarfile exposes both member information and file-like streams. These examples build small trusted archives in memory, then inspect or consume their contents without calling extract or extractall.

Prepare a scratch directory

These standalone examples were tested on Linux with Python 3.12.14. Use Python 3.8 or newer. No archive files are written to the current directory. Use Bash, a text editor, and the standard mktemp, rm, and rmdir utilities. No third-party packages are required. Run without -O so the example assertions remain enabled.

Create a fresh directory and keep using the same terminal:

start_dir="$PWD"
work_dir="$(mktemp -d)" || exit 1
cd -- "$work_dir" || exit 1

Save each named file below as UTF-8. Do not name your script after a standard-library module.

1. Separate regular files from directories

Save as list_tar_member_metadata.py:

from io import BytesIO
import tarfile

buffer = BytesIO()
with tarfile.open(fileobj=buffer, mode="w") as archive:
    folder = tarfile.TarInfo("reports")
    folder.type = tarfile.DIRTYPE
    archive.addfile(folder)
    data = b"ready\n"
    member = tarfile.TarInfo("reports/status.txt")
    member.size = len(data)
    archive.addfile(member, BytesIO(data))
buffer.seek(0)
with tarfile.open(fileobj=buffer, mode="r:") as archive:
    for member in archive.getmembers():
        kind = "file" if member.isfile() else "directory" if member.isdir() else "other"
        print(f"{member.name}: {kind}, {member.size} bytes")

Run it:

python3 -B list_tar_member_metadata.py

Output:

reports: directory, 0 bytes
reports/status.txt: file, 6 bytes

TarInfo describes a member; reading the metadata does not create its path. Member types matter because a tar archive can contain links and special entries as well as ordinary files. A displayed size is metadata supplied by the archive, not a trust guarantee.

2. Read one regular member with a size limit

Save as read_selected_tar_member.py:

from io import BytesIO
import tarfile

buffer = BytesIO()
with tarfile.open(fileobj=buffer, mode="w") as archive:
    data = b"build=passed\n"
    member = tarfile.TarInfo("reports/summary.txt")
    member.size = len(data)
    archive.addfile(member, BytesIO(data))
buffer.seek(0)
with tarfile.open(fileobj=buffer, mode="r:") as archive:
    matches = [item for item in archive.getmembers()
               if item.name == "reports/summary.txt"]
    if len(matches) != 1 or not matches[0].isfile():
        raise ValueError("Expected one regular summary file")
    member = matches[0]
    limit = 1024
    if member.size > limit:
        raise ValueError("Summary is too large")
    with archive.extractfile(member) as stream:
        data = stream.read(limit + 1)
    if len(data) > limit:
        raise ValueError("Summary exceeded the read limit")
    print(data.decode("utf-8"), end="")
    print("Read bytes:", len(data))

Run it:

python3 -B read_selected_tar_member.py

Output:

build=passed
Read bytes: 13

extractfile returns a readable object; despite its name, it does not unpack the member onto disk. This example rejects duplicate names, non-regular members, and an oversized selected member before decoding. Check both the declared size and the bounded read. It is still a demonstration on a trusted archive, not a complete hostile-archive validator.

3. Process members in archive order

Save as stream_tar_records.py:

from io import BytesIO
import tarfile

buffer = BytesIO()
with tarfile.open(fileobj=buffer, mode="w") as archive:
    for name, data in [("logs/build.txt", b"start\ndone\n"),
                       ("logs/test.txt", b"passed\n")]:
        member = tarfile.TarInfo(name)
        member.size = len(data)
        archive.addfile(member, BytesIO(data))
buffer.seek(0)
with tarfile.open(fileobj=buffer, mode="r|") as archive:
    total = 0
    for member in archive:
        if not member.isfile():
            continue
        with archive.extractfile(member) as stream:
            count = sum(1 for line in stream)
        total += count
        print(f"{member.name}: {count} lines")
    print("Total lines:", total)
    assert total == 3

Run it:

python3 -B stream_tar_records.py

Output:

logs/build.txt: 2 lines
logs/test.txt: 1 lines
Total lines: 3

The vertical bar selects sequential stream mode. Read the current member while it is available rather than collecting names and seeking backward later. Iterating lines avoids one large read of the entire file, but a single long line can still consume substantial memory. The example uses an uncompressed in-memory tar; r|* can detect supported compression.

Avoiding filesystem extraction removes path-writing concerns from these examples, not every archive risk. Malformed or compressed input can still consume CPU and memory; metadata lists can be large. For external archives, bound total bytes, member count, member size, and processing time as appropriate. If unpacking is actually required, review the current extraction-filter documentation separately instead of changing these examples to extractall.

Clean up

In the same terminal, remove only the named sources, return to the original directory, and remove the empty scratch directory:

rm -- list_tar_member_metadata.py read_selected_tar_member.py stream_tar_records.py
cd -- "$start_dir" || exit 1
rmdir -- "$work_dir"
unset start_dir work_dir

If rmdir reports remaining files, inspect them instead of deleting unknown contents.

References

Acknowledgements

This post was created with assistance from AI.

Last updated on