System.Formats.Tar
| Language | C# (.NET 7 and later) |
|---|---|
| License | MIT (part of the .NET runtime) |
| Repository | github.com/dotnet/runtime |
| Documentation | System.Formats.Tar namespace |
| Depends on | nothing (in the base class library); System.IO.Compression for gzip |
| Read / write | yes / yes; v7, ustar, pax and GNU formats; no compression of its own |
System.Formats.Tar ships with .NET 7 and later. It has 3 levels:
TarFile: staticCreateFromDirectoryandExtractToDirectory, for whole directories.TarReaderandTarWriter: entry-by-entry streaming over anyStream.TarEntryand its subclassesPaxTarEntry,GnuTarEntry,UstarTarEntry,V7TarEntry: one entry with its name, mode, owner, times, link target andDataStream.
It writes pax by default, so long names, large files and nanosecond times work. It doesn’t compress: combine it with GZipStream (or BrotliStream, ZLibStream) from System.IO.Compression. For .tar.xz or .tar.zst use SharpCompress or a zstd package.
Installation#
Nothing to install on .NET 7 or later: add using System.Formats.Tar;. It isn’t available for .NET Framework or .NET 6.
Basic usage: create and extract a .tar.gz#
using System.Formats.Tar;
using System.IO.Compression;
// create backup.tar.gz from the "docs" folder
using (FileStream fs = File.Create("backup.tar.gz"))
using (var gz = new GZipStream(fs, CompressionLevel.Optimal))
{
TarFile.CreateFromDirectory("docs", gz, includeBaseDirectory: true);
}
// extract it into "out"
Directory.CreateDirectory("out");
using (FileStream fs = File.OpenRead("backup.tar.gz"))
using (var gz = new GZipStream(fs, CompressionMode.Decompress))
{
TarFile.ExtractToDirectory(gz, "out", overwriteFiles: true);
}
For an uncompressed .tar pass file paths directly: TarFile.CreateFromDirectory("docs", "docs.tar", false) and TarFile.ExtractToDirectory("docs.tar", "out", true). ExtractToDirectory throws if an entry would land outside the destination folder.
How to list the files in a tar archive#
using System.Formats.Tar;
using FileStream fs = File.OpenRead("archive.tar");
using var reader = new TarReader(fs);
TarEntry? entry;
while ((entry = reader.GetNextEntry()) != null)
{
Console.WriteLine($"{entry.EntryType,-16} {entry.Length,10} {entry.ModificationTime:u} {entry.Name}");
}
entry.Format tells you whether the entry is V7, Ustar, Pax or Gnu.
How to read one file from a tar into memory#
GetNextEntry(copyData: true) copies the data so it stays readable after the reader moves on; with the default false, read DataStream before the next call.
using System.Formats.Tar;
using System.Text;
static string? ReadText(string tarPath, string name)
{
using FileStream fs = File.OpenRead(tarPath);
using var reader = new TarReader(fs);
while (reader.GetNextEntry() is TarEntry entry)
{
if (entry.Name == name && entry.DataStream != null)
{
using var sr = new StreamReader(entry.DataStream, Encoding.UTF8);
return sr.ReadToEnd();
}
}
return null;
}
How to write entries from memory and from files#
using System.Formats.Tar;
using System.Text;
using FileStream fs = File.Create("out.tar");
using var writer = new TarWriter(fs, TarEntryFormat.Pax, leaveOpen: false);
// a file from disk, stored as "data/report.csv"
writer.WriteEntry("report.csv", "data/report.csv");
// a directory entry
writer.WriteEntry(new PaxTarEntry(TarEntryType.Directory, "notes/"));
// a file from memory, with mode and time
byte[] bytes = Encoding.UTF8.GetBytes("hello\n");
var entry = new PaxTarEntry(TarEntryType.RegularFile, "notes/hello.txt")
{
DataStream = new MemoryStream(bytes),
Mode = UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.GroupRead | UnixFileMode.OtherRead,
ModificationTime = DateTimeOffset.UtcNow,
};
writer.WriteEntry(entry);
// disposing the writer writes the end-of-archive blocks
How to extract entries one by one#
using System.Formats.Tar;
string dest = Path.GetFullPath("out");
using FileStream fs = File.OpenRead("archive.tar");
using var reader = new TarReader(fs);
while (reader.GetNextEntry() is TarEntry entry)
{
string target = Path.GetFullPath(Path.Combine(dest, entry.Name));
if (!target.StartsWith(dest + Path.DirectorySeparatorChar))
continue; // skip entries that escape the folder
if (entry.EntryType == TarEntryType.Directory)
Directory.CreateDirectory(target);
else if (entry.EntryType is TarEntryType.RegularFile or TarEntryType.V7RegularFile)
{
Directory.CreateDirectory(Path.GetDirectoryName(target)!);
entry.ExtractToFile(target, overwrite: true);
}
}
Tips#
TarEntryFormat.Gnuwrites GNU long-name entries for tools that don’t read pax;Ustarfails on names over 255 bytes.PaxTarEntry.ExtendedAttributesexposes pax key/value pairs such asmtimeandSCHILY.xattr.*. Write aPaxGlobalExtendedAttributesTarEntryfor attributes that apply to all following entries.- Async versions exist for everything:
TarFile.CreateFromDirectoryAsync,TarReader.GetNextEntryAsync,TarWriter.WriteEntryAsync.