archive/tar
| Language | Go |
|---|---|
| License | BSD 3-clause (Go standard library) |
| Repository | github.com/golang/go/src/archive/tar |
| Documentation | pkg.go.dev/archive/tar |
| Depends on | nothing (standard library) |
| Read / write | yes / yes; ustar, pax and GNU (reading also star and v7); sparse files on read; no compression of its own |
archive/tar is a streaming TAR reader and writer in the Go standard library. tar.Reader walks the entries of any io.Reader and is itself an io.Reader for the current entry’s data. tar.Writer writes a tar.Header followed by the data. Docker, containerd, Go modules tooling and many backup tools use it.
When writing, it picks the format per header: ustar when the fields fit, pax when a name is long, a time has sub-second precision, or a number is too large (or GNU if you set Header.Format). It has no compression; wrap the file in gzip.NewReader / gzip.NewWriter.
Installation#
Nothing to install: import "archive/tar".
Basic usage: list and read a .tar.gz#
package main
import (
"archive/tar"
"compress/gzip"
"errors"
"fmt"
"io"
"log"
"os"
)
func main() {
f, err := os.Open("archive.tar.gz")
if err != nil {
log.Fatal(err)
}
defer f.Close()
gz, err := gzip.NewReader(f)
if err != nil {
log.Fatal(err)
}
defer gz.Close()
tr := tar.NewReader(gz)
for {
hdr, err := tr.Next()
if errors.Is(err, io.EOF) {
break // end of archive
}
if err != nil {
log.Fatal(err)
}
fmt.Printf("%c %10d %s %s\n", hdr.Typeflag, hdr.Size, hdr.ModTime.Format("2006-01-02"), hdr.Name)
if hdr.Name == "README.md" {
data, err := io.ReadAll(tr) // tr reads the current entry's data
if err != nil {
log.Fatal(err)
}
fmt.Println(string(data))
}
}
}
How to extract a tar archive safely#
os.Root (Go 1.25+ for Root.MkdirAll) confines all file operations to one directory, so names with .. or absolute paths and symlinks that point outside fail:
package main
import (
"archive/tar"
"errors"
"io"
"io/fs"
"os"
"path/filepath"
)
func extract(r io.Reader, dir string) error {
if err := os.MkdirAll(dir, 0o755); err != nil {
return err
}
root, err := os.OpenRoot(dir)
if err != nil {
return err
}
defer root.Close()
tr := tar.NewReader(r)
for {
hdr, err := tr.Next()
if errors.Is(err, io.EOF) {
return nil
}
if err != nil {
return err
}
name := filepath.FromSlash(hdr.Name)
switch hdr.Typeflag {
case tar.TypeDir:
if err := root.MkdirAll(name, 0o755); err != nil {
return err
}
case tar.TypeReg:
if err := root.MkdirAll(filepath.Dir(name), 0o755); err != nil {
return err
}
mode := fs.FileMode(hdr.Mode).Perm()
out, err := root.OpenFile(name, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, mode)
if err != nil {
return err
}
_, err = io.Copy(out, tr)
out.Close()
if err != nil {
return err
}
default:
// skip symlinks, hard links and devices from untrusted archives
}
}
}
func main() {
f, err := os.Open("archive.tar")
if err != nil {
panic(err)
}
defer f.Close()
if err := extract(f, "out"); err != nil {
panic(err)
}
}
How to create a .tar.gz from a folder#
Writer.AddFS (Go 1.22+) adds every file of an fs.FS:
package main
import (
"archive/tar"
"compress/gzip"
"log"
"os"
)
func main() {
out, err := os.Create("docs.tar.gz")
if err != nil {
log.Fatal(err)
}
defer out.Close()
gz := gzip.NewWriter(out)
tw := tar.NewWriter(gz)
if err := tw.AddFS(os.DirFS("docs")); err != nil {
log.Fatal(err)
}
// close in order: tar (writes end blocks), then gzip (writes the trailer)
if err := tw.Close(); err != nil {
log.Fatal(err)
}
if err := gz.Close(); err != nil {
log.Fatal(err)
}
}
AddFS stores paths relative to the fs.FS root and skips the root itself. It fails on device files and sockets.
How to write files from memory#
var files = []struct{ Name, Body string }{
{"readme.txt", "This archive contains text files.\n"},
{"notes/todo.txt", "Write more examples.\n"},
}
tw := tar.NewWriter(w) // w is any io.Writer
for _, f := range files {
hdr := &tar.Header{
Name: f.Name,
Mode: 0o644,
Size: int64(len(f.Body)),
ModTime: time.Now(),
}
if err := tw.WriteHeader(hdr); err != nil {
return err
}
if _, err := tw.Write([]byte(f.Body)); err != nil {
return err
}
}
return tw.Close()
How to build a header from a file on disk#
tar.FileInfoHeader fills mode, size, times and link type from os.FileInfo; set Name to the path you want inside the archive:
fi, err := os.Lstat(path)
if err != nil {
return err
}
hdr, err := tar.FileInfoHeader(fi, "") // second arg: symlink target, if any
if err != nil {
return err
}
hdr.Name = filepath.ToSlash(relPath)
hdr.Format = tar.FormatPAX // optional: force pax for every entry
Tips#
- Since Go 1.20,
tar.Reader.Nextreturnstar.ErrInsecurePathtogether with the header for names like../xor/etc/xwhenGODEBUG=tarinsecurepath=0is set. Check names yourself or useos.Rootas above. hdr.PAXRecordsholds extra pax keys such asSCHILY.xattr.user.comment, both when reading and writing.- For
.tar.zstusezstd.NewReader(f)fromgithub.com/klauspost/compress/zstdin place ofgzip.NewReader.