opscape

Archives and compression

zip

Package and compress files into a .zip archive, the format every operating system opens natively.

zip [OPTION...] ARCHIVE[.zip] [FILE...]

11 of 11 examples run in Debian 13.7 on 2026-10-31 · manual: zip(1)

zip archive.zip files... creates (or adds to) a zip archive. Unlike a .tar.gz, a zip compresses each file separately and keeps a central index, so a single member can be listed or extracted without reading the rest, and Windows and macOS open it with a double-click. That makes zip the format for sending files to people; for backups and Unix-to-Unix transfers, tar keeps more metadata (owners, symlinks, special files).

Two habits matter. First, zip does not descend into directories unless you pass -r: zip out.zip dir stores just the empty directory entry. Second, an existing archive is updated, not replaced: zip adds new names and replaces matching ones, but never removes members on its own. Delete the old zip first if you want a fresh one, or use -FS to sync it with the disk.

Paths are stored as given, so run zip from the directory you want the archive to be relative to. Extract with unzip.

Examples

Zip a few files

zip notes.zip notes.txt todo.txt
explain
output
  adding: notes.txt (deflated 23%)
  adding: todo.txt (deflated 13%)

adding: lines show each file and how much deflate compression saved. The .zip suffix is added automatically if you leave it off.

Zip a directory with -r

zip -r src.zip src
explain
output
  adding: src/ (stored 0%)
  adding: src/Makefile (deflated 14%)
  adding: src/app.py (deflated 35%)
  adding: src/util.sh (deflated 14%)
  adding: src/main.c (deflated 5%)
  adding: src/lib/ (stored 0%)
  adding: src/lib/helpers.py (deflated 7%)

-r recurses. Directories are stored as empty entries (stored 0%) so they are recreated on extraction.

The empty-directory surprise

zip src.zip src
unzip -l src.zip
output
  adding: src/ (stored 0%)
Archive:  src.zip
  Length      Date    Time    Name
---------  ---------- -----   ----
        0  2026-10-26 01:56   src/
---------                     -------
        0                     1 file

Without -r, zip adds only the directory entry itself. The archive contains one empty directory and nothing else.

Leave files out with -x

zip -rq data.zip data -x '*.json' '*.conf'
unzip -l data.zip
output
Archive:  data.zip
  Length      Date    Time    Name
---------  ---------- -----   ----
        0  2026-10-25 01:33   data/
       65  2026-10-25 17:38   data/hosts.txt
       27  2026-10-26 09:05   data/numbers.txt
       20  2026-10-26 18:06   data/windows.txt
       59  2026-10-26 08:21   data/inventory.tsv
       78  2026-10-26 20:12   data/poem.txt
      405  2026-10-26 08:37   data/sales.csv
      436  2026-10-27 08:26   data/users.csv
       66  2026-10-25 17:04   data/fruits.txt
---------                     -------
     1156                     9 files

-q hides the adding: lines. -x takes one or more patterns, matched against the stored paths; quote them so the shell does not expand them first. Both JSON files and the .conf file were skipped.

Store files without their directories

zip -j flat.zip src/lib/helpers.py src/main.c
unzip -l flat.zip
output
  adding: helpers.py (deflated 7%)
  adding: main.c (deflated 5%)
Archive:  flat.zip
  Length      Date    Time    Name
---------  ---------- -----   ----
       88  2026-10-28 07:32   helpers.py
       83  2026-10-28 15:30   main.c
---------                     -------
      171                     2 files

-j ("junk paths") stores only the base names, so the archive is flat. Two files with the same name in different directories would collide.

Add a file to an existing archive

Setup run before this example
zip -q notes.zip notes.txt
zip notes.zip todo.txt
unzip -l notes.zip
output
  adding: todo.txt (deflated 13%)
Archive:  notes.zip
  Length      Date    Time    Name
---------  ---------- -----   ----
      190  2026-10-25 05:30   notes.txt
       62  2026-10-24 21:28   todo.txt
---------                     -------
      252                     2 files

Zipping into an existing archive adds to it; notes.txt from before is still there.

Refresh only changed files with -u

Setup run before this example
zip -qr src.zip src
sleep 2; echo '/* v2 */' >> src/main.c
zip -ru src.zip src
explain
output
updating: src/main.c (deflated 4%)

-u replaces members whose file on disk is newer than the copy in the archive and adds new files. Only main.c changed, so only it is updating:.

Remove a member from an archive

Setup run before this example
zip -q notes.zip notes.txt todo.txt
zip -d notes.zip todo.txt
unzip -l notes.zip
output
deleting: todo.txt
Archive:  notes.zip
  Length      Date    Time    Name
---------  ---------- -----   ----
      190  2026-10-25 05:30   notes.txt
---------                     -------
      190                     1 file

-d deletes the named members from the archive in place; the files on disk are not touched.

Zip a list of files from find

find . -name '*.py' | zip -q python.zip -@
unzip -l python.zip
output
Archive:  python.zip
  Length      Date    Time    Name
---------  ---------- -----   ----
       88  2026-10-28 07:32   src/lib/helpers.py
      363  2026-10-28 09:43   src/app.py
---------                     -------
      451                     2 files

-@ reads the file names to add from standard input, one per line, so find can choose them. zip drops the leading ./ from each name.

Test an archive

Setup run before this example
zip -qr src.zip src
zip -T src.zip
explain
output
test of src.zip OK

-T runs a full integrity test of the archive (zip calls unzip -t for this) and reports OK or the damaged members.

A name that does not exist

zip out.zip nope.txt
explain
outputexit 12
	zip warning: name not matched: nope.txt

zip error: Nothing to do! (out.zip)

zip warns about each unmatched name. When nothing at all could be added it stops with Nothing to do! and exit status 12, and no archive is created.

Common pitfalls

  • zip out.zip dir without -r stores an empty directory. Always use zip -r for directories.

  • Re-running zip -r out.zip dir after deleting files from dir leaves the deleted files in the archive. Remove out.zip first, or use zip -r -FS out.zip dir to make the archive match the disk.

  • zip does not keep Unix owners, and symlinks are followed (their targets stored) unless you add -y. Use tar when that metadata matters.

  • zip -e encryption is the legacy ZipCrypto scheme, which is weak. For real protection, encrypt with a dedicated tool.

GNU vs BSD and macOS

The examples above ran the GNU/Linux version. These are the differences that matter on macOS and the BSDs (written from their documentation, not run here).

  • Linux distributions and macOS both ship Info-ZIP zip 3.0, so options behave the same. macOS Finder's "Compress" adds a __MACOSX/ folder with resource forks; command-line zip does not.

Options worth knowing

A selection, described in our own words. For the full list, see zip(1) or zip --help.

OptionWhat it does
-r, --recurse-pathsRecurse into directories.
-q, --quietDo not print the adding: lines.
-v, --verbosePrint more detail.
-j, --junk-pathsStore only file names, without directory paths.
-x, --exclude PATTERNLeave out files matching the patterns that follow.
-i, --include PATTERNAdd only files matching the patterns that follow.
-u, --updateReplace members only if the file on disk is newer; add new files.
-f, --freshenReplace members that are newer on disk; do not add new files.
-FS, --filesyncMake the archive match the disk, deleting members whose files are gone.
-d, --deleteDelete the named members from the archive.
-m, --moveDelete the original files after adding them.
-@, --names-stdinRead the names of files to add from standard input.
-T, --testTest the new archive's integrity, and undo the change if the test fails.
-e, --encryptEncrypt with a password (prompted; weak legacy encryption).
-y, --symlinksStore symbolic links as links instead of the files they point to.
-0Store without compression.
-9Compress as much as possible (default level is 6).
-sf, --show-filesList the files that would be operated on, then exit.

Related