ZetCode

Linux zip Command

last modified October 23, 2026

The zip command creates ZIP archives, and unzip lists, tests, and extracts them. Unlike tar, which bundles files and leaves compression to a separate program, ZIP compresses each file individually and keeps an index at the end of the archive. Single files can therefore be listed, extracted, updated, or deleted without unpacking the rest. ZIP is the most widely supported archive format on Windows and macOS, which makes it a good choice for sharing files with other systems.

The syntax is zip [options] archive.zip file.... The examples use Info-ZIP Zip 3.0 and UnZip 6.0 on Debian, with a few text files, a small website directory, a 100 KB JPEG file, and a 3 MB binary file.

Creating an archive

The first argument is the archive name, followed by the files to add.

zip archive.zip report.txt
ls -l report.txt archive.zip

For each file, zip prints the method it used and how much space it saved. The repetitive text file shrinks by 95%, from 10692 to 727 bytes. If the archive name has no extension, zip adds .zip.

$ zip archive.zip report.txt
  adding: report.txt (deflated 95%)
$ ls -l report.txt archive.zip
-rw-r--r-- 1 jano jano   727 Oct 23 13:30 archive.zip
-rw-r--r-- 1 jano jano 10692 Oct 20 10:00 report.txt

Adding files to an archive

Running zip on an existing archive adds new files and replaces the ones already in it.

zip docs.zip notes.txt todo.txt
zip docs.zip *.txt

The second command updates notes.txt and todo.txt and adds report.txt. Very small files are stored (kept uncompressed) because compression would not make them smaller.

$ zip docs.zip notes.txt todo.txt
  adding: notes.txt (stored 0%)
  adding: todo.txt (stored 0%)
$ zip docs.zip *.txt
updating: notes.txt (stored 0%)
updating: todo.txt (stored 0%)
  adding: report.txt (deflated 95%)

Archiving directories

To include the contents of a directory, the -r (recurse) option is required.

zip nodir.zip website
zip -r site.zip website

Without -r, only an empty directory entry is added, which is a common mistake. With -r, all files and subdirectories are included, with their paths relative to the current directory.

$ zip nodir.zip website
  adding: website/ (stored 0%)
$ zip -r site.zip website
  adding: website/ (stored 0%)
  adding: website/css/ (stored 0%)
  adding: website/css/style.css (stored 0%)
  adding: website/cache/ (stored 0%)
  adding: website/cache/page1.tmp (stored 0%)
  adding: website/js/ (stored 0%)
  adding: website/js/app.js (stored 0%)
  adding: website/access.log (stored 0%)
  adding: website/index.html (deflated 16%)

Listing the contents

The -l option of unzip lists the files in an archive without extracting them.

unzip -l site.zip
zipinfo -1 site.zip

The listing shows the uncompressed size, the modification time, and the name of each entry. zipinfo -1 prints only the names, which is handy in scripts. unzip -v and plain zipinfo show more details, such as the compressed size and the method.

$ unzip -l site.zip
Archive:  site.zip
  Length      Date    Time    Name
---------  ---------- -----   ----
        0  2026-10-21 14:30   website/
        0  2026-10-21 14:30   website/css/
       20  2026-10-21 14:30   website/css/style.css
        0  2026-10-21 14:30   website/cache/
        4  2026-10-21 14:30   website/cache/page1.tmp
        0  2026-10-21 14:30   website/js/
       19  2026-10-21 14:30   website/js/app.js
       10  2026-10-21 14:30   website/access.log
       32  2026-10-21 14:30   website/index.html
---------                     -------
       85                     9 files
$ zipinfo -1 site.zip
website/
website/css/
website/css/style.css
website/cache/
website/cache/page1.tmp
website/js/
website/js/app.js
website/access.log
website/index.html

Excluding files

The -x option excludes files that match the given patterns.

zip -r site-clean.zip website -x '*.log' 'website/cache/*'

The patterns are quoted so that zip, not the shell, matches them against the paths stored in the archive. In zip patterns, * also matches slashes, so '*.log' excludes log files at any depth. The -i option works the other way and includes only matching files.

$ zip -r site-clean.zip website -x '*.log' 'website/cache/*'
  adding: website/ (stored 0%)
  adding: website/css/ (stored 0%)
  adding: website/css/style.css (stored 0%)
  adding: website/js/ (stored 0%)
  adding: website/js/app.js (stored 0%)
  adding: website/index.html (deflated 16%)

Updating and freshening

The -u option adds new files and replaces changed ones. The -f option only replaces changed files that are already in the archive.

echo "p { color: navy; }" >> website/css/style.css
zip -r -u site.zip website
echo "new page" > website/about.html
zip -r -f site.zip website
unzip -l site.zip | tail -n 1

zip -u compares modification times and rewrites only style.css. The new file about.html is not added by -f; only the directory entry, whose time changed when the file was created, is freshened. The archive still has 9 entries.

$ echo "p { color: navy; }" >> website/css/style.css
$ zip -r -u site.zip website
updating: website/css/style.css (deflated 5%)
$ echo "new page" > website/about.html
$ zip -r -f site.zip website
freshening: website/ (stored 0%)
$ unzip -l site.zip | tail -n 1
      104                     9 files

Deleting files from an archive

The -d option removes entries from an archive.

zip -d site.zip 'website/cache/*' website/access.log
unzip -l site.zip | tail -n 1

The archive is rewritten without the cache directory and the log file. The files on disk are not touched.

$ zip -d site.zip 'website/cache/*' website/access.log
deleting: website/cache/
deleting: website/cache/page1.tmp
deleting: website/access.log
$ unzip -l site.zip | tail -n 1
       90                     6 files

Compression levels

The options -0 to -9 set the compression level. -0 stores files without compression, -9 compresses best, and the default is -6.

zip -q -0 store.zip report.txt
zip -q -9 best.zip report.txt
ls -l store.zip best.zip
zip media.zip photo.jpg

The -q option suppresses the progress messages. Higher levels save little on most data and take more time. Formats that are already compressed, such as JPEG, MP4, or other ZIP files, do not shrink at all, as the last command shows; storing them with -0 is faster.

$ zip -q -0 store.zip report.txt
$ zip -q -9 best.zip report.txt
$ ls -l store.zip best.zip
-rw-r--r-- 1 jano jano   731 Oct 23 13:30 best.zip
-rw-r--r-- 1 jano jano 10862 Oct 23 13:30 store.zip
$ zip media.zip photo.jpg
  adding: photo.jpg (deflated 0%)

Archiving without paths

The -j (junk paths) option stores only the file names, without their directories.

zip -j flat.zip website/index.html website/css/style.css
unzip -l flat.zip

The files end up in the top level of the archive. If two files have the same name in different directories, only one of them can be stored this way.

$ zip -j flat.zip website/index.html website/css/style.css
  adding: index.html (deflated 16%)
  adding: style.css (deflated 5%)
$ unzip -l flat.zip
Archive:  flat.zip
  Length      Date    Time    Name
---------  ---------- -----   ----
       32  2026-10-21 14:30   index.html
       39  2026-10-23 13:30   style.css
---------                     -------
       71                     2 files

Password protection

The -e option encrypts the files and asks for a password twice.

zip -e secret.zip notes.txt
unzip -l secret.zip
unzip -P wrong secret.zip -d out1
unzip -P s3cret secret.zip -d out1

The password is not echoed. The file names stay visible, only the contents are encrypted. Without -P, unzip asks for the password; -P passes it on the command line, where other users can see it in the process list, so use it only for tests.

zip uses the traditional PKWARE encryption (ZipCrypto), which is weak and can be broken with known-plaintext attacks. For sensitive data, use an AES-encrypted archive created with 7-Zip, or encrypt the archive with gpg.

$ zip -e secret.zip notes.txt
Enter password:
Verify password:
  adding: notes.txt (stored 0%)
$ unzip -l secret.zip
Archive:  secret.zip
  Length      Date    Time    Name
---------  ---------- -----   ----
       34  2026-10-20 10:00   notes.txt
---------                     -------
       34                     1 file
$ unzip -P wrong secret.zip -d out1
Archive:  secret.zip
   skipping: notes.txt               incorrect password
$ unzip -P s3cret secret.zip -d out1
Archive:  secret.zip
 extracting: out1/notes.txt

Splitting an archive

The -s option splits an archive into parts of a given size, for example for upload limits.

zip -q -s 1m big.zip video.bin
ls -l big.*
unzip -q big.zip -d out2
cat big.z01 big.z02 big.zip > joined.zip
unzip -q joined.zip -d out2
cmp video.bin out2/video.bin && echo identical

The parts are named .z01, .z02, and so on, and the last one is .zip. UnZip 6.0 cannot read split archives directly. Joined in order with cat, the parts can be extracted; unzip prints warnings, but cmp confirms that the file is intact. The documented way to rejoin the parts is zip -s 0 big.zip --out whole.zip, but in our test, Zip 3.0 produced a corrupt archive when there were more than two parts. Always verify the result.

$ zip -q -s 1m big.zip video.bin
$ ls -l big.*
-rw-r--r-- 1 jano jano 1048576 Oct 23 13:30 big.z01
-rw-r--r-- 1 jano jano 1048576 Oct 23 13:30 big.z02
-rw-r--r-- 1 jano jano  903480 Oct 23 13:30 big.zip
$ unzip -q big.zip -d out2
warning [big.zip]:  zipfile claims to be last disk of a multi-part archive;
  attempting to process anyway, assuming all parts have been concatenated
  together in order.  Expect "errors" and warnings...true multi-part support
  doesn't exist yet (coming soon).
file #1:  bad zipfile offset (local header sig):  4
$ cat big.z01 big.z02 big.zip > joined.zip
$ unzip -q joined.zip -d out2
warning [joined.zip]:  zipfile claims to be last disk of a multi-part archive;
  attempting to process anyway, assuming all parts have been concatenated
  together in order.  Expect "errors" and warnings...true multi-part support
  doesn't exist yet (coming soon).
warning [joined.zip]:  2097152 extra bytes at beginning or within zipfile
  (attempting to process anyway)
file #1:  bad zipfile offset (local header sig):  2097156
  (attempting to re-compensate)
$ cmp video.bin out2/video.bin && echo identical
identical

Reading file names from standard input

The -@ option reads the list of files to add from standard input.

find website -name '*.html' -o -name '*.css' | zip -@ web-src.zip

This lets find select the files by any criteria: name, size, age, or owner. The new about.html is included.

$ find website -name '*.html' -o -name '*.css' | zip -@ web-src.zip
  adding: website/about.html (stored 0%)
  adding: website/css/style.css (deflated 5%)
  adding: website/index.html (deflated 16%)

Extracting archives

unzip extracts into the current directory, or into the directory given with -d.

unzip site.zip -d restore
unzip -n site.zip -d restore
unzip -o -q site.zip -d restore
unzip -q site.zip website/css/style.css -d only-css
find only-css -type f
unzip -p site.zip website/css/style.css

If a file already exists, unzip asks whether to replace it. -n never overwrites existing files, and -o overwrites them without asking. File names after the archive name extract only those entries. The -p option writes the contents to standard output, so a file can be read without creating it on disk.

$ unzip site.zip -d restore
Archive:  site.zip
   creating: restore/website/
   creating: restore/website/css/
  inflating: restore/website/css/style.css
   creating: restore/website/js/
 extracting: restore/website/js/app.js
  inflating: restore/website/index.html
$ unzip -n site.zip -d restore
Archive:  site.zip
$ unzip -o -q site.zip -d restore
$ unzip -q site.zip website/css/style.css -d only-css
$ find only-css -type f
only-css/website/css/style.css
$ unzip -p site.zip website/css/style.css
body { margin: 0; }
p { color: navy; }

Testing an archive

The -t option of unzip checks the integrity of every entry without extracting anything.

unzip -t site.zip
head -c 400 site.zip > truncated.zip
unzip -t truncated.zip

For a good archive, each entry is reported as OK. The truncated copy is missing the central directory at the end of the file, so unzip cannot read it at all. Test archives after transferring them and before deleting the originals.

$ unzip -t site.zip
Archive:  site.zip
    testing: website/                 OK
    testing: website/css/             OK
    testing: website/css/style.css    OK
    testing: website/js/              OK
    testing: website/js/app.js        OK
    testing: website/index.html       OK
No errors detected in compressed data of site.zip.
$ head -c 400 site.zip > truncated.zip
$ unzip -t truncated.zip
Archive:  truncated.zip
  End-of-central-directory signature not found.  Either this file is not
  a zipfile, or it constitutes one disk of a multi-part archive.  In the
  latter case the central directory and zipfile comment will be found on
  the last disk(s) of this archive.
unzip:  cannot find zipfile directory in one of truncated.zip or
        truncated.zip.zip, and cannot find truncated.zip.ZIP, period.

Best practices for zip

Source

Info-ZIP home page

In this article, we have used the zip and unzip commands to create, list, update, encrypt, split, extract, and test ZIP archives.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Linux tutorials.