Python Basics: Read Tar Members Without Extracting Files
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 1Save 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.pyOutput:
reports: directory, 0 bytes
reports/status.txt: file, 6 bytesTarInfo 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.pyOutput:
build=passed
Read bytes: 13extractfile 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 == 3Run it:
python3 -B stream_tar_records.pyOutput:
logs/build.txt: 2 lines
logs/test.txt: 1 lines
Total lines: 3The 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_dirIf rmdir reports remaining files, inspect them instead of deleting unknown contents.
References
Acknowledgements
This post was created with assistance from AI.