Home / Alt manpages / zip(1)

  • zip(1)
  • User command
  • linux

Create, update and verify ZIP archives with zip

You will finish with a ZIP archive containing selected files, a safe way to add newer files later, and a verification step that checks the archive rather than trusting the command prompt. The examples use Info-ZIP zip 3.0, installed here as Debian package version 3.0-13ubuntu0.2.

Allow about fifteen minutes. You need a shell and read access to the files you want to archive. These commands normally run as your own user. Use sudo only when the source files are genuinely restricted, and keep the destination writable by the account running zip.

Checkpoint

This guide creates and changes archive files, but does not delete your source data unless you deliberately choose the separate -m option. The ordinary examples leave the originals untouched.

1. Check the installed command

Confirm which binary will run and record its version before relying on an option in a script:

$ command -v zip
/usr/bin/zip
$ zip -v | sed -n '1,8p'
Copyright (c) 1990-2008 Info-ZIP - Type 'zip "-L"' for software license.
This is Zip 3.0 (July 5th 2008), by Info-ZIP.
Currently maintained by E. Gordon.
Compiled with gcc 13.3.0 for Unix (Linux ELF).
Zip special compilation options:
        USE_EF_UT_TIME       (store Universal Time)
        BZIP2_SUPPORT        (bzip2 library version 1.0.8, 13-Jul-2019)

The package query gives the distribution package version. The two version strings describe different things: the first is the upstream program release, while the second is the package revision that installed it.

2. Create an archive from named files

The basic form is zip ARCHIVE FILE.... If the archive name has no extension, this release adds .zip:

$ zip project.zip README.md LICENSE
  adding: README.md (stored 0%)
  adding: LICENSE (deflated 42%)

The output is deliberately dependent on the input. Text may be deflated, while a file that would not get smaller can be stored. Check the result with unzip -l if the unzip package is installed:

$ unzip -l project.zip
Archive:  project.zip
  Length      Date    Time    Name
---------  ---------- -----   ----
     1234  2026-09-28 12:00   README.md
      987  2026-09-28 12:00   LICENSE
---------                     -------
     2221                     2 files

The dates and lengths above are examples of the fields to inspect, not values to expect on your machine. The archive stores each entry's name, path, modification time, permissions and integrity information.

3. Archive a directory tree

Use -r to recurse into a directory. The directory name becomes part of each stored path:

$ zip -r project.zip project/
  adding: project/ (stored 0%)
  adding: project/README.md (deflated 42%)
  adding: project/src/main.c (deflated 55%)

Shell globbing is an easy trap here. A pattern such as * does not normally match names beginning with a dot, and it does not recurse into subdirectories by itself. If you need a complete tree, name the directory and use -r. If you intentionally want selected top-level paths, quote or expand them deliberately and inspect the listing afterwards.

To store the contents without the leading directory path, use -j with a file list:

$ zip -j project-files.zip project/*
  adding: README.md (deflated 42%)
  adding: main.c (deflated 55%)

Flattening paths can create duplicate names. Do not use -j for a tree unless losing directory structure is intentional.

4. Exclude files before they enter the archive

Use -x for exclusions. Quote wildcard patterns so the shell does not expand them against the current directory:

$ zip -r project.zip project -x 'project/.git/*' 'project/build/*'
  adding: project/README.md (deflated 42%)
  adding: project/src/main.c (deflated 55%)

Patterns are matched against the stored path, so include the directory prefix when the archive contains it. Verify the exclusion instead of assuming it worked:

$ unzip -l project.zip | grep -E '(^|/)(\.git|build)(/|$)' || echo 'excluded paths not present'
excluded paths not present

If the input contains secrets, generated credentials or private keys, stop and review the list before creating a distributable archive. Encryption is not a substitute for checking what you are about to share.

5. Update only files that became newer

The default mode replaces matching entries and adds new files. Use -u when an existing archive should receive only files that are newer on disk, plus new files:

$ zip -u project.zip project/README.md project/src/main.c
updating: project/README.md (deflated 42%)

Use -f instead when you want to freshen existing entries but never add names that are absent from the archive. Both modes compare the file timestamps used by zip, so a copied file with an older timestamp may not be selected even when its contents differ.

When changing an existing archive, zip writes a temporary replacement and replaces the old archive only after successful creation. That protects against many interrupted updates, but it is not a backup. Copy an important archive before a large or unusual change.

6. Test the archive and inspect failures

Run -T to test the archive's contents and integrity:

$ zip -T project.zip
test of project.zip OK

A successful test confirms that zip could read the entries and their checksums matched. It does not prove that the archive contains every file you intended, so pair it with unzip -l or zip -sf:

$ zip -sf project.zip
Archive contains:
  project/
  project/README.md
  project/src/main.c
Total 3 entries (2 files)

-sf scans and shows the files that would be processed without changing the archive. It is useful before a large command, especially when patterns or exclusions are involved. If a source is unreadable, zip normally warns and continues. Treat a warning as a failed backup until you have investigated it; use the exact path in the message to check permissions and availability.

7. Remove entries only with a recovery plan

Warning

-d removes matching entries from the archive. It does not delete the source files, but the archive change is still destructive. Make a copy first:

$ cp --preserve=all project.zip project-before-delete.zip
$ zip -d project.zip 'project/build/*'
deleting: project/build/output.o
$ zip -T project.zip
test of project.zip OK

To undo this archive edit, replace it with the copy only after checking that the copy is the version you want:

$ mv project.zip project-after-delete.zip
$ mv project-before-delete.zip project.zip

Quote patterns passed to -d. An unquoted wildcard may be expanded by the shell into unrelated names from the current directory, turning a precise archive operation into a different one.

8. Avoid hidden defaults in scripts

zip reads default options from the ZIPOPT environment variable. An inherited value can change a command that looks complete when read in a script. Inspect it before troubleshooting surprising behaviour:

$ printf 'ZIPOPT=%s\n' "${ZIPOPT-}"
ZIPOPT=

For repeatable automation, run with a known environment and use explicit options. Keep archive paths and input paths quoted when they contain spaces. Do not put passwords or private archive contents in shell history. The installed manpage describes encryption options, but encrypted ZIP files have compatibility and password-handling risks; choose a separate, reviewed encryption workflow when confidentiality is a requirement.

Done means

  • The archive was created with the intended files and path layout.
  • Wildcard exclusions were quoted and checked with an archive listing.
  • Updates used -u or -f for the intended timestamp behaviour.
  • zip -T passed, and the listing was checked for omissions.
  • Any deletion used a verified backup and has a clear restore command.
  • The command's version and the ZIPOPT environment were understood.