1. Overview

In this tutorial, we’ll be looking at the zip command-line tool in Linux.

2. zip

The zip command is a command-line tool in Linux that allows us to create an archive of files and directories. Besides that, it also provides a multitude of functionalities for manipulating an archive.

2.1. Installation

To install the zip command-line tool in Debian-based Linux, we can use the package manager apt-get:

$ sudo apt-get update -qq
$ sudo apt-get install -y zip

Additionally, we can use the package manager yum to install the zip command-line tool in RHEL-based Linux:

$ sudo yum update -qq
$ sudo yum install -y zip

2.2. General Syntax

Generally, we can express the zip command as:

$ zip [options] [zipfile [files...]] [-xi list]

First, the zip command accepts a list of optional flags. These flags serve as different options to run the command. Then, the argument zipfile specifies the filename of an archive. Next, the files arguments specify a list of filenames.

Finally, the optional flags -xi define exclusion and inclusion filters. Additionally, this set of flags must be defined at the end of the command.

2.3. Basic Usage

One of the simplest use cases of the zip command is to create an archive of some files. For example, we can create an archive important-backup.zip that contains the files credentials.txt and statement.txt using zip:

$ zip important-backup.zip credentials.txt statement.txt
  adding: credentials.txt (stored 0%)
  adding: statement.txt (stored 0%)

3. Listing Archive Contents Without Unarchiving

Sometimes, we might only want to take a glance at the content of an archive without unzipping it. This can be easily achieved using the zipinfo and unzip commands, both of which come with the installation of zip.

To view the content of an archive using zipinfo, we can run the command with the archive filename as its sole argument:

$ zipinfo important-backup.zip
Archive:  important-backup.zip
Zip file size: 347 bytes, number of entries: 2
-rw-r--r--  3.0 unx        8 tx stor 21-May-03 11:22 credentials.txt
-rw-r--r--  3.0 unx        5 tx stor 21-May-03 11:22 statement.txt
2 files, 13 bytes uncompressed, 13 bytes compressed:  0.0%

Similarly, we can use the unzip command to list the content of an archive directly:

$ unzip -l important-backup.zip
Archive:  important-backup.zip
  Length      Date    Time    Name
---------  ---------- -----   ----
        8  2021-05-03 11:22   credentials.txt
        5  2021-05-03 11:22   statement.txt
---------                     -------
       13                     2 files

With the flag -l, the command does not extract the archive and, instead, only lists its content.

4. Deleting Files From Archive Without Unzipping

To delete some entries in an archive without unzipping, we can pass the flag -d to the zip command. For example, we can delete the statement.txt file from the important-backup.zip archive using zip -d:

$ zip -d important-backup.zip statement.txt
deleting: statement.txt

Let’s take a look at the content of the archive important-backup.zip after the command returns:

$ zipinfo important-backup.zip
Archive:  important-backup.zip
Zip file size: 188 bytes, number of entries: 1
-rw-r--r--  3.0 unx        8 tx stor 21-May-03 11:22 credentials.txt
1 file, 8 bytes uncompressed, 8 bytes compressed:  0.0%

As we can see, statement.txt has been removed from the archive without having to first extract the archive’s contents.

5. Appending to an Existing Archive

The zip command provides the option to append additional entries to an existing archive using the -g flag.

For example, let’s say we have an existing archive, important-backup.zip:

$ zipinfo important-backup.zip
Archive:  important-backup.zip
Zip file size: 188 bytes, number of entries: 1
-rw-r--r--  3.0 unx        8 tx stor 21-May-03 11:22 credentials.txt

We can add two more entries into the important-backup.zip archive by passing the flag -g to the zip command:

$ zip -g important-backup.zip bank-accounts.txt customer-details.txt
  adding: bank-accounts.txt (stored 0%)
  adding: customer-details.txt (stored 0%)

In the example above, we’re adding the new entries bank-accounts.txt and customer-details.txt into the important-backup.zip. Now, the archive will consist of three entries in total:

$ zipinfo important-backup.zip
Archive:  important-backup.zip
Zip file size: 551 bytes, number of entries: 3
-rw-r--r--  3.0 unx        8 tx stor 21-May-03 11:22 credentials.txt
-rw-r--r--  3.0 unx       17 tx stor 21-May-03 11:49 bank-accounts.txt
-rw-r--r--  3.0 unx       16 tx stor 21-May-03 11:49 customer-details.txt
3 files, 41 bytes uncompressed, 41 bytes compressed:  0.0%

If the specified archive file doesn’t already exist, the zip command will create it.

Let’s run the zip -g command with a non-existent archive filename:

$ zip -g total-new-archive.zip bank-accounts.txt customer-details.txt
        zip warning: total-new-archive.zip not found or empty
  adding: bank-accounts.txt (stored 0%)
  adding: customer-details.txt (stored 0%)

We can see that the zip command prints a warning, telling us the archive total-new-archive.zip is not found. Nevertheless, it creates and puts the bank-accounts.txt and customer-details.txt into the archive.

6. Encrypting an Archive

The zip command allows us to encrypt an archive using the flag -e. With the flag -e, the command will ask for a password that serves as an encryption key for the encrypted archive.

Let’s create and encrypt an archive file encrypted-backup.zip:

$ zip -e encrypted-backup.zip bank-accounts.txt credentials.txt
Enter password:
Verify password:
  adding: bank-accounts.txt (stored 0%)
  adding: credentials.txt (stored 0%)

With the flag -e, the command will now prompt us for a password that serves as the key for encryption.

If we now attempt to unzip the archive, it’ll ask for a password to unarchive it:

$ unzip encrypted-backup.zip
Archive:  encrypted-backup.zip
[encrypted-backup.zip] bank-accounts.txt password:

However, we can still list the content of an encrypted archive using tools like zipinfo and unzip without knowing the password:

$ zipinfo encrypted-backup.zip
Archive:  encrypted-backup.zip
  Length      Date    Time    Name
---------  ---------- -----   ----
       17  2021-05-03 11:49   bank-accounts.txt
        8  2021-05-03 11:22   credentials.txt
---------                     -------
       25                     2 files

As we can see, it doesn’t prompt us for the password when we attempt to read its content listing.

7. Traversing Subdirectories Recursively

By default, the zip command doesn’t archive a directory recursively. By using the flag -r, zip will traverse subdirectories recursively and archive their files.

For example, let’s say we have a directory prod-secret that contains some secret files:

$ ls prod-secret
kafka-passwd.secret  mongo-passwd.secret  mysql-passwd.secret

To create an archive of the entire prod-secret directory, we can run the zip command with flag -r:

$ zip -r prod-secret.zip prod-secret
  adding: prod-secret/ (stored 0%)
  adding: prod-secret/mysql-passwd.secret (stored 0%)
  adding: prod-secret/mongo-passwd.secret (stored 0%)
  adding: prod-secret/kafka-passwd.secret (stored 0%)

Running the command with option -r ensures the files within the directory are included as well.

However, the zip command will ignore the files within the prod-secret directory without the flag -r:

$ zip prod-secret prod-secret
  adding: prod-secret/ (stored 0%)

8. Including and Excluding Files Pattern

The zip command allows us to construct filters to include or exclude files. Furthermore, these filters are defined with the glob pattern. Additionally, we can use these filters in either creation, deletion, or freshen mode.

Let’s say we are working in the following directory:

$ tree -a
.
|-- .git
|   |-- HEAD
|   |-- branch
|   `-- tag
|-- credentials.txt
|-- customer-details.txt
`-- prod-secret
    |-- .git
    |   |-- HEAD
    |   `-- branch
    |-- kafka-passwd.secret
    |-- mongo-passwd.secret
    `-- mysql-passwd.secret

3 directories, 10 files

Using the tree command, we display the structure of our directory in a graphical form. As we can see, there is a .git subfolder in the current directory and one in the prod-secret directory.

8.1. Including Files and Folders Matching a Pattern

Using the flag -i, we can create an inclusion filter. When we define an inclusion filter, zip command will only consider files that match the filter pattern.

Let’s create an archive only-git.zip that consists of only the .git subfolder:

$ zip -r only-git.zip . -i *.git*
  adding: prod-secret/.git/ (stored 0%)
  adding: prod-secret/.git/branch (stored 0%)
  adding: prod-secret/.git/HEAD (stored 0%)
  adding: .git/ (stored 0%)
  adding: .git/tag (stored 0%)
  adding: .git/branch (stored 0%)
  adding: .git/HEAD (stored 0%)

From the output, we can see that the zip command only archives folders and files that match the glob.

8.2. Excluding Files and Folders Matching a Pattern

To create an exclusion filter, we can use the flag -x followed by a glob pattern. With the exclusion filter, zip will exclude files that match the pattern. Particularly, we can create an archive that excludes the .git folder using zip -x:

$ zip -r no-git.zip . -x *.git*
  adding: prod-secret/ (stored 0%)
  adding: prod-secret/mysql-passwd.secret (stored 0%)
  adding: prod-secret/mongo-passwd.secret (stored 0%)
  adding: prod-secret/kafka-passwd.secret (stored 0%)
  adding: customer-details.txt (stored 0%)
  adding: credentials.txt (stored 0%)

As we can observe from the output, the command did not add either of the .git folders into the archive no-git.zip.

8.3. Note About Path Evaluation

Whenever zip evaluates the file and directory paths for inclusion and exclusion, it’s always evaluating the full relative path. In other words, the resulting archive would be empty if the glob pattern is .git*:

$ zip -r only-git.zip . -i .git*
        zip warning: zip file empty

That’s because the exact path of the .git folder in the current working directory is ./.git/, which doesn’t match the glob pattern .git*. Similarly, the full relative path of the .git folder inside the prod-secret directory is ./prod-secret/.git, which also doesn’t match the glob pattern. Hence, neither of the folders are added to the archive.

9. Formatting Progress Output Message

By default, the zip command reports the progress of the operation through standard output. There are several options available to us for customizing this output.

9.1. Suppressing Progress Output

Using the flag -q, we can prevent the zip command from displaying any output message:

$ zip -q -r all.zip .

As we can see, the zip command doesn’t output any progress message when the flag -q is present.

9.2. Displaying Progress in Bytes

To display the progress output in terms of the number of bytes to be archived, we can pass the flag -db:

$ zip -r -db all.zip .
[   0/  91]   adding: prod-secret/ (stored 0%)
[   0/  91]   adding: prod-secret/mysql-passwd.secret (stored 0%)
[  13/  78]   adding: prod-secret/mongo-passwd.secret (stored 0%)
[  26/  65]   adding: prod-secret/kafka-passwd.secret (stored 0%)
[  39/  52]   adding: prod-secret/.git/ (stored 0%)
[  39/  52]   adding: prod-secret/.git/branch (stored 0%)
[  46/  45]   adding: prod-secret/.git/HEAD (stored 0%)
[  51/  40]   adding: customer-details.txt (stored 0%)
[  67/  24]   adding: credentials.txt (stored 0%)
[  75/  16]   adding: .git/ (stored 0%)
[  75/  16]   adding: .git/tag (stored 0%)
[  79/  12]   adding: .git/branch (stored 0%)
[  86/   5]   adding: .git/HEAD (stored 0%)

With the flag -db, the zip command precedes the progress output with a square bracket containing two numbers. The number on the left represents the number of bytes that have been archived, while the number on the right shows the number of bytes left to be archived.

9.3. Displaying Progress in Counts

Alternatively, we can display the progress in terms of files and directory counts using option flag -dc:

$ zip -r -dc all.zip .
  0/ 13 updating: prod-secret/ (stored 0%)
  1/ 12 updating: prod-secret/mysql-passwd.secret (stored 0%)
  2/ 11 updating: prod-secret/mongo-passwd.secret (stored 0%)
  3/ 10 updating: prod-secret/kafka-passwd.secret (stored 0%)
  4/  9 updating: prod-secret/.git/ (stored 0%)
  5/  8 updating: prod-secret/.git/branch (stored 0%)
  6/  7 updating: prod-secret/.git/HEAD (stored 0%)
  7/  6 updating: customer-details.txt (stored 0%)
  8/  5 updating: credentials.txt (stored 0%)
  9/  4 updating: .git/ (stored 0%)
 10/  3 updating: .git/tag (stored 0%)
 11/  2 updating: .git/branch (stored 0%)
 12/  1 updating: .git/HEAD (stored 0%)

The number on the left shows the number of files that have been archived thus far, whereas the number on the right shows the number of files waiting to be archived.

10. Moving Files and Folders Into Archive

With option -m, the zip command will move the files and directory into the archive. In other words, the files and folders that have been archived will be removed.

Let’s run the zip command with the option -m on our directory:

$ ls
credentials.txt   customer-details.txt  prod-secret
$ zip -m backup.zip credentials.txt
  adding: credentials.txt (stored 0%)
$ ls
backup.zip  customer-details.txt  prod-secret

As we can see, after moving the file credentials.txt into the archive backup.zip, the file credentials.txt is removed.

11. Freshening an Archive

The zip command also supports freshen mode through the flag -f. In this mode, the zip command will not add new files into an archive. Instead, it will only update the files within the archive.

For example, the current directory contains an archive credentials-archive.zip and a few other files:

$ ls -l
total 16
-rw-r--r-- 1 david david    17 May  3 21:22 credentials.txt
-rw-r--r-- 1 david david   197 May  3 21:23 credentials-archive.zip
-rw-r--r-- 1 david david    16 May  3 16:17 customer-details.txt
drwxr-xr-x 3 david david  4096 May  3 16:17 prod-secret

Furthermore, the archive credentials-archive.zip contains only the file credentials.txt:

$ zipinfo credentials-archive.zip
Archive:  credentials-archive.zip
Zip file size: 197 bytes, number of entries: 1
-rw-r--r--  3.0 unx       17 tx stor 21-May-03 21:22 credentials.txt
1 file, 17 bytes uncompressed, 17 bytes compressed:  0.0%

Now, let’s modify the credentials.txt file by adding a new line into it:

$ echo "id=1234;password=4321" >> credentials.txt
$ ls -l credentials.txt
-rw-r--r-- 1 root root 39 May  3 21:27 credentials.txt

Notice that both the timestamp and size of the file credentials.txt are updated.

Now, we can run the zip command in freshening mode to update the credentials.txt file in the archive:

$ zip -r -f credentials-archive.zip .
freshening: credentials.txt (deflated 5%)

Although we’ve specified the flag -r in the command, it doesn’t bring in additional new files into the archive. This is because when using freshen mode, the zip command will not add new files to an archive. If we had not run the command in freshen mode, the zip command would’ve included everything in the current directory in the archive.

Let’s look at the updated credentials-archive.zip archive:

$ zipinfo credentials-archive.zip
Archive:  credentials-archive.zip
Zip file size: 217 bytes, number of entries: 1
-rw-r--r--  3.0 unx       39 tx defN 21-May-03 21:27 credentials.txt
1 file, 39 bytes uncompressed, 37 bytes compressed:  5.1%

As we can observe, the zip command updates the files in the archive to the latest version. Additionally, it doesn’t include the rest of the files within the same directory.

12. Conclusion

In this tutorial, we’ve taken a thorough look into the zip command-line tool.

We’ve started with a basic installation guide along with the syntax structure and basic usage. Then, we’ve introduced the tools for peeking at the content of an archive without unzipping.

Next, we’ve demonstrated some options that allow us to modify the existing archive. Additionally, we’ve also shown some options related to creating an archive, including use of the recursive flag and filters for inclusion and exclusion.

Besides that, we’ve seen different options for modifying the progress output produced by the zip command. Finally, we’ve looked at the freshen mode provided by the zip command.