Skip to content
Latchkey

zip -u and -@: Update or Build From a File List

zip -u updates an existing zip in place, and zip -@ reads the names of files to add from standard input, one path per line.

When only a few files change, updating an archive can beat rebuilding it; when the file set comes from find or git, piping it into zip -@ is cleaner than a giant argument list.

What it does

zip -u (update) replaces an archive entry only if the source file is newer than the stored one, and also adds files not yet in the archive. zip -f (freshen) only replaces existing entries and never adds new ones. zip -@ tells zip to read the input paths from stdin, one per line, so you archive exactly the set a previous command produced. -u requires the archive to already exist.

Common usage

Terminal
# update changed files and add new ones
zip -u build.zip dist/changed.js dist/new.js
# freshen: only replace files already in the archive
zip -f build.zip dist/changed.js
# build from a piped file list (changed files only)
git diff --name-only HEAD~1 | zip changed.zip -@

Options

FlagWhat it does
-uUpdate: replace newer files and add new ones
-fFreshen: replace existing entries only, add nothing
-@Read the file list from standard input
-dDelete entries from the archive
-rRecurse into directories

In CI

Updating relies on file modification times, which are unreliable across fresh checkouts where every file has the checkout time; if mtimes are identical, -u may skip the replacement, so rebuilding is often simpler for deterministic results. With -@, each line is a path relative to the current directory, so pipe a clean newline-separated list and run from the right cwd. -@ pairs naturally with find or git to build incremental archives of just the changed files.

Common errors in CI

Running zip -u on a path that does not exist creates a new archive rather than erroring, which can mask a wrong filename. If updates are silently skipped, the source mtime is not newer than the stored entry; touch the file or rebuild. With -@, "zip warning: name not matched" means the piped paths do not resolve relative to the current directory, and empty stdin produces "zip error: Nothing to do!". Caching an archive and updating it leaves stale entries if a file was deleted, since -u never removes entries.

Related guides

Run this faster and cheaper on Latchkey managed runners. Start free →