tar Command in Linux: Create, List, and Extract Archives

tar Command in Linux: Create, List, and Extract Archives

tar turns a folder into a single file on any Linux system, and the same command compresses it on the way.

Understanding the option string is the whole job, because tar -czvf stacks four unrelated jobs into one word.

What the letters in tar -czvf are doing

Read the string as four jobs rather than as a word. A single letter chooses the operation, another adds compression, -v asks for a running list, and -f binds the archive file that comes next.

Only -f changes how the shell reads the next argument, which is why it has to sit immediately before the archive name and nowhere else.

# one operation letter, always
tar -cf backup.tar project      # create
tar -xf backup.tar              # extract
tar -tf backup.tar              # list
tar -rf backup.tar newfile.txt  # append
tar --delete -f backup.tar old.txt

# compression letters, added when you create or extract
-z  gzip    -j  bzip2    -J  xz    --zstd  zstd    -a  pick from the suffix

The dash is optional, and GNU tar 1.35 accepts tar cvf just as happily. I keep the dashed form because -czvf stays readable when it sits next to a file name.

Inside the archive, tar stores a header per member with the path, the mode, the owning user and group, the size, and a modification time. Nothing in that list needs a compressor, which is the split between tar and the tools it feeds.

zip compresses while it packs, so one program and one suffix covers the whole job. tar writes the archive first and hands the result to gzip, bzip2, xz or zstd as a second step, and that two-stage design is what produces the double suffix .tar.gz.

tar -cvf project.tar project printing every member it adds
tar -cvf on a five-file folder, with each member printed as it is added

Each name in that listing appeared as tar reached it, which is the only thing -v changes. Strip the letter and the same archive is written in silence.

What you need before the first command

Nothing here needs root, and nothing written below leaves the directory you run it in.

  • GNU tar 1.35 or newer, which is what Ubuntu 24.04 LTS ships
  • a folder with a few files in it, shallow enough to read the listings
  • gzip, bzip2, xz and zstd on the system if you plan to use those compression letters
  • write access to the directory you extract into

Checking the version is worth the second it takes, because the wording of tar’s refusals has changed across releases. tar –version prints the one you have, the GNU tar manual carries every option the letters can spell, and the man command reads the same reference offline.

I kept the test tree shallow on purpose, so every member path in the listings stays short enough to follow. A deep tree hides the lesson in wrapped lines.

Create the archive and pick the format

Create writes a new archive from the names you give it. The operation is -c, and -f takes the file name that follows.

$ tar -cvf project.tar project
project/
project/docs/
project/docs/README.md
project/src/
project/src/app.yml
project/src/schema.sql
project/hotfix.txt
project/VERSION

The folder keeps its own name inside the archive, so an extract puts project/ back the way it was. Nothing is compressed at this stage, and the result is a plain tar.

Compression is one more letter in the same string, and I used one folder for all four formats so the sizes line up.

tar -czf project.tar.gz  project    # gzip
tar -cjf project.tar.bz2 project    # bzip2
tar -cJf project.tar.xz  project    # xz
tar --zstd -cf project.tar.zst project

I measured all four on the same folder rather than quoting a published comparison, because compression ratios move with the content and a five-file tree behaves differently from a source checkout.

ArchiveLetterSize on this tree
project.tarnone10,240 bytes
project.tar.gz-z427 bytes
project.tar.bz2-j465 bytes
project.tar.xz-J452 bytes
project.tar.zst–zstd385 bytes

A plain ls listing shows the padding straight away, because the plain tar is 10,240 bytes for 199 bytes of files. tar pads an archive out to a 10 KiB block boundary. That padding comes from the format’s tape-drive history, where a shorter record wasted the same tape space.

ls -l comparing the plain tar, gzip and xz archives for one folder
The same folder as a plain tar, a .tar.gz and a .tar.xz

With -a, tar reads the suffix you already typed and picks the compressor itself.

$ tar -caf auto.tar.gz project
$ file auto.tar.gz
auto.tar.gz: gzip compressed data, from Unix, original size modulo 2^32 10240

I use -a inside scripts, because then the name and the format cannot drift apart.

The compression letters have pages of their own, and the gzip command covers what gzip does when you run it without tar at all. If you would rather stay with an archiver you already know from Windows, zip and unzip is the closer comparison.

-C moves tar into another directory before it collects files. The archive still lands where you ran the command, which is the end of the line readers point the flag at when it seems to do nothing.

$ tar -czf elsewhere.tar.gz -C /tmp/tarprobe VERSION
$ tar -tzf elsewhere.tar.gz
VERSION

For a long file list, -T reads the names from a file so the shell does not have to expand them.

$ cat files.txt
project/src/app.yml
project/VERSION
$ tar -czf picked.tar.gz -T files.txt
$ tar -tzf picked.tar.gz
project/src/app.yml
project/VERSION

To leave something out, name it the way the archive will store it. The stored path is the string tar compares, not the name you see on disk.

$ tar -czf nodocs.tar.gz --exclude='project/docs' project
$ tar -tzf nodocs.tar.gz
project/
project/src/
project/src/app.yml
project/src/schema.sql
project/hotfix.txt
project/VERSION

A name with no slash in it is matched against the last part of the member path, so –exclude=’*.md’ drops every Markdown file under the tree.

Reading a size listing is its own small skill, and the du command covers what those tools report for a directory that has not been archived yet.

Look inside without unpacking

-t reads the archive index and writes nothing to disk. Add -v and tar prints the stored mode, owner, size and timestamp beside each name.

CommandWhat it prints
tar -tf project.tarmember names only
tar -tvf project.tarmode, owner, size, timestamp, name
tar -tzf project.tar.gzmember names from a gzip archive
tar -tJf project.tar.xzmember names from an xz archive
tar -tf project.tar.zst –zstdmember names from a zstd archive
$ tar -tvf project.tar
drwxrwxr-x ubuntu/ubuntu     0 2026-09-16 10:43 project/
drwxrwxr-x ubuntu/ubuntu     0 2026-09-16 10:43 project/docs/
-rw-rw-r-- ubuntu/ubuntu    63 2026-09-16 10:43 project/docs/README.md
drwxrwxr-x ubuntu/ubuntu     0 2026-09-16 10:43 project/src/
-rw-rw-r-- ubuntu/ubuntu    39 2026-09-16 10:43 project/src/app.yml
-rw-rw-r-- ubuntu/ubuntu    28 2026-09-16 10:43 project/src/schema.sql
-rw-rw-r-- ubuntu/ubuntu    31 2026-09-16 10:43 project/hotfix.txt
-rw-rw-r-- ubuntu/ubuntu    16 2026-09-16 10:43 project/VERSION

The owner column is the account that built the archive, not the one reading it, and the mode field is what an extract will try to restore. Both matter as soon as a different user opens the file, which is where chmod and chown take over.

A listing also answers the question an extraction failure raises, because it prints the exact member path tar compares against. I read the owner column before extracting an archive that was built on another machine, and I copy the member path from here instead of reconstructing it from memory.

Extract where you want the files

-x writes members back onto disk, into the directory you are standing in unless -C names somewhere else.

$ mkdir -p restored
$ tar -xvf project.tar.gz -C restored
project/
project/docs/
project/docs/README.md
project/src/
project/src/app.yml
project/src/schema.sql
project/hotfix.txt
project/VERSION
tar -xvf extracting a gzip archive with no z in the option string
tar -xvf on a .tar.gz, with the compression detected from the file header

That command has no z in it and the archive is gzip. GNU tar reads the first bytes, recognizes the gzip header, and starts the matching decompressor by itself. The compression letters matter when you create an archive, not when you open one.

-C changes directory before extracting and does not create the target, so make the folder first. I keep -C in scripts rather than a cd, because a directory change does not survive to the next command on the same line.

$ tar -xvf project.tar.gz -C restored
tar: restored: Cannot open: No such file or directory
tar: Error is not recoverable: exiting now

A single member comes out on its own when you name its exact stored path. tar walks the archive and stops when the path matches.

$ tar -xf project.tar project/src/schema.sql -C one
$ find one -type f
one/project/src/schema.sql

Shell wildcards need telling, because extraction matches names exactly by default.

$ tar -xf project.tar --wildcards '*/schema.sql' -C wild
$ tar -xf project.tar '*/schema.sql' -C wild
tar: Pattern matching characters used in file names
tar: Use --wildcards to enable pattern matching, or --no-wildcards to suppress this warning
tar: */schema.sql: Not found in archive

To read a member without writing it anywhere, -O sends the contents to standard output.

$ tar -xzf project.tar.gz -O project/VERSION
release 2026.09

Archives built from a project root carry a leading folder, which is the situation behind the question about extracting into a directory that should already exist. –strip-components drops that many leading path parts on the way out.

$ tar -xzf project.tar.gz -C flat --strip-components=1
$ find flat -type f | sort
flat/VERSION
flat/docs/README.md
flat/hotfix.txt
flat/src/app.yml
flat/src/schema.sql

An absolute path behaves differently again. tar prints Removing leading `/’ from member names and stores the path without the slash, so a tarball cannot write into /etc when root opens it.

Add and remove members later

-r appends to an archive that already exists, and –delete takes one member out. Both edit the file in place, and both stop on a compressed archive.

$ printf 'CHANGELOG for 2026.09\n' > project/CHANGELOG.md
$ tar -rvf project.tar project/CHANGELOG.md
project/CHANGELOG.md
tar -rvf appending one new member to an existing plain tar archive
tar -rvf reports the member it appended
$ tar --delete -f project.tar project/VERSION
$ tar -tf project.tar
project/
project/docs/
project/docs/README.md
project/src/
project/src/app.yml
project/src/schema.sql
project/hotfix.txt
project/CHANGELOG.md

Point either verb at a .tar.gz and the answer is the same refusal.

$ tar --delete -f project.tar.gz project/VERSION
tar: Cannot update compressed archives
tar: Error is not recoverable: exiting now

Compression wraps the whole archive in one stream, so a change in the middle would mean rewriting every block after it. tar refuses rather than leaving you with a file that only looks edited.

The long way around works and takes three commands.

$ gunzip -k editme.tar.gz
$ tar --delete -f editme.tar project/VERSION
$ gzip -f editme.tar
$ tar -tzf editme.tar.gz
project/
project/docs/
project/docs/README.md
project/src/
project/src/app.yml
project/src/schema.sql
project/hotfix.txt
Edit you wantPlain .tarCompressed .tar.gz
add a filetar -rvf backup.tar newfile.txt-r refuses, rebuild the archive
remove a membertar –delete -f backup.tar old.txt–delete refuses, rebuild the archive
change a memberappend it again and the later copy winsextract, edit, recreate

I ran the gunzip route on the same tree to confirm the refusal is about the file rather than about permissions. gunzip -k keeps the original, and the three commands leave a working archive behind.

The failures you will hit first

These refusals account for the tar questions that keep arriving on support forums. Each one exits with status 2 and stops before it writes anything.

MessageWhat happenedFix
gzip: stdin: not in gzip formata compression letter was given for a plain tardrop -z, or recreate the archive with it
tar: Cannot update compressed archives-r or –delete was pointed at a .tar.gzextract, edit, and create the archive again
tar: Cannot open: No such file or directory-C named a directory that does not existmkdir -p the target first
Removing leading `/’ from member namesan absolute path was archivedexpected, and the stored path is relative
gzip: stdin: unexpected end of filethe archive is truncatedcopy or download the whole file again
gzip reporting not in gzip format when -z is used on a plain tar archive
The refusal a compression letter produces on a plain .tar
tar refusing to delete a member from a compressed archive
The same refusal covers -r and –delete on any compressed archive

The first message costs people an hour because it names gzip rather than tar. gzip is only the child process tar started, the archive was plain, and the letter said otherwise.

Splitting a large archive across several files is a separate task, and split tar archives covers it. A truncated file fails differently and looks worse than it is. tar prints whatever it managed to read before the stream ended and then exits 2, so a partial listing in the output is a warning rather than a finished read.

When –exclude leaves a file in place, the exclusion never matched the stored member name. Print the members with -tf and copy the path from there.

A single-page lookup for the gzip half of that first refusal is worth keeping, and the gzip is not in gzip format page walks through it on its own.

Test the archive before you delete the source

A tarball is a single point of failure for everything inside it, and the check takes a second. Run a listing and a stream test before the source directory goes away.

$ tar -tf project.tar.gz > /dev/null && echo listing ok
listing ok
$ gzip -t project.tar.gz && echo integrity ok
integrity ok
$ tar -tf project.tar.bz2 > /dev/null && bzip2 -t project.tar.bz2 && echo bzip2 ok
bzip2 ok
$ tar -tf project.tar.xz > /dev/null && xz -t project.tar.xz && echo xz ok
xz ok

The stream test reads the compressed data and verifies its internal checksum, which is what catches a truncated transfer that a listing alone would pass. All four tools stay silent on success, so the trailing echo is the only visible confirmation.

Once both checks pass, the archive is safe to move and the folder is safe to remove. I run both against an archive before the source directory goes away, because a tarball that fails a checksum at restore time is worth nothing.

Questions readers ask about tar

Answering these in one place keeps the sections above free of repetition.

What is the tar command in Linux?

tar collects files and directories into one archive file, and optionally hands that archive to a compressor such as gzip or xz. The name comes from tape archive, because the tool was written to write files onto magnetic tape.

What does -f do in a tar command?

-f tells tar that the next word on the command line is the archive file. It is the only tar letter that consumes the following argument, so it has to sit immediately before the archive name.

Which command untars a .tar.gz file?

tar -xzf file.tar.gz extracts it. Plain tar -xf file.tar.gz also works, because GNU tar reads the compression header and starts the right decompressor on its own.

Do I need -z to extract a .tar.xz or a .tar.bz2?

No. The compression letters are needed when you create an archive. Extraction detects gzip, bzip2, xz and zstd from the file header, so -xf opens all four.

Does tar compress by default?

No. tar -cf writes an uncompressed archive, which is why a plain .tar of a small folder is padded out to 10 KiB. Add -z, -j, -J or –zstd, or use -a to let the suffix choose.

How do I extract a single file from an archive?

Name the member’s stored path after the archive, as in tar -xf project.tar project/src/schema.sql. Use tar -tf first if you do not know the exact path the archive stored.