28

Forgive this most basic question, but I couldn't find a direct answer or simple example on stack exchange.

Let's say I have a folder of files that I would like to compress into one zip file that I can share with my terrible Windows friends (otherwise I'd just use tar and be done with it). It looks like this:

.
├── file1.txt
├── file2.txt
├── file3.txt
├── file.jpg
└── test.jpg

Assuming I have zip installed:

sudo apt-get install zip

I see from man zip that it allows me to use a file list:

-@ file lists. If a file list is specified as -@ [Not on MacOS], zip takes the list of input files from standard input instead of from the command line. For example,

          zip -@ foo

So I created zip.lst which looks like this:

cat zip.lst

file1.txt
file2.txt
file3.txt
file.jpg
test.jpg

And now I tried:

zip -@ zip.lst

But it didn't do anything except create a blank line on the console. And I can keep pushing Enter and it just keeps making more blank lines without seemingly executing the command. After some searching, I realized that I needed to terminate input by pressing Ctrl+D

But now I get this error:

    zip warning: missing end signature--probably not a zip file (did you
    zip warning: remember to use binary mode when you transferred it?)
    zip warning: (if you are trying to read a damaged archive try -F)

zip error: Zip file structure invalid (zip.lst)

What!? How is my simple list not a "valid file structure"? Back to the manual for more information, and I take a closer look at:

If a file list is specified as -@...

So I try it without -@, and that at least processes it right away without having to press Ctrl+D but I'm left with the same error.

Luckily, I found a comment on an answer to a nearly unrelated question that led me to realize that I needed to explicitly name the zip file. This error'd because by default it was trying to create a zip file called zip.lst. But since that's the name of my list, one might think that it would simply overwrite it, but no, it was in fact trying to update it. And since the list obviously isn't a zip file, we get the invalid file structure error, and now it's clear why it said "probably not a zip file." So then I tried:

zip files.zip zip.lst

adding: zip.lst (deflated 35%)

Eureka! I can see the zip file was created and my folder now contains this:

.
├── file1.txt
├── file2.txt
├── file3.txt
├── file.jpg
├── files.zip
├── test.jpg
└── zip.lst

But wait before we celebrate, let's confirm the contents of the zip file first:

unzip -l files.zip

Archive:  files.zip
  Length      Date    Time    Name
---------  ---------- -----   ----
       48  2016-05-24 15:30   zip.lst
---------                     -------
       48                     1 file

No! Well, actually that makes sense from standard usage because it just zipped up the one list file I gave it. Finally I tried again with -@ but got the same result.

What am I doing wrong?

BTW, I know I can use the GUI and do it with my mouse, but I need to script this, and in general I'm faster on the CLI when I know what I'm doing.

2 Answers 2

42

Use file < redirection

zip files.zip -@ < zip.lst

Or you could skip the list and just glob

zip files.zip *.txt *.jpg

thanks steeldriver

Historical Answer

I'm glad it's OK to answer my own question because I wanted to chronicle my turmoil in hopes that others following behind in my trail of tears could be spared by searching some key words.

Eventually, I discovered the right answer, but it was posted to the wrong question.

A couple points of clarity:

  • The manual isn't referring to a literal list as in a file, but rather literally to listing the files out on the console.
  • It's understandable that someone might be misled into thinking that foo referred to the file list, but it's actually intended to represent the name of the output zip file.
  • The list of files is omitted in the example because it's using the -@ switch, which demonstrates it "takes the list of input files from standard input instead of from the command line"

Understanding this, we are now armed with a variety of ways to accomplish the goal.

Firstly using the file list we created, we can read out the list with cat and redirect the output with a pipe as standard input to zip:

cat zip.lst | zip -@ files.zip

  adding: file1.txt (stored 0%)
  adding: file2.txt (stored 0%)
  adding: file3.txt (stored 0%)
  adding: file.jpg (stored 0%)
  adding: test.jpg (stored 0%)

Seeing that adding: ... for each file is certainly a good sign, but we've had our hopes up before, so let's just confirm:

unzip -l files.zip

Archive:  files.zip
  Length      Date    Time    Name
---------  ---------- -----   ----
        0  2016-05-24 14:54   file1.txt
        0  2016-05-24 14:54   file2.txt
        0  2016-05-24 14:54   file3.txt
        0  2016-05-24 15:00   file.jpg
        0  2016-05-24 14:59   test.jpg
---------                     -------
        0                     5 files

SWEET! But who wants to create a onetime-use list anyway? That's an awful round-about way of solving this particular scenario. It would be better to get a list from ls and pipe that to zip:

ls | zip -@ files.zip

  adding: file1.txt (stored 0%)
  adding: file2.txt (stored 0%)
  adding: file3.txt (stored 0%)
  adding: file.jpg (stored 0%)
  adding: test.jpg (stored 0%)

Fabulous! Now let's create filters with the awesome power of grep to just zip up the jpg's for instance:

ls | grep .jpg | zip -@ images.zip

  adding: file.jpg (stored 0%)
  adding: test.jpg (stored 0%)

Or just files beginning with "file"

ls | grep file. | zip -@ files.zip

  adding: file1.txt (stored 0%)
  adding: file2.txt (stored 0%)
  adding: file3.txt (stored 0%)
  adding: file.jpg (stored 0%)
4
  • 5
    Instead of using ls and grep and zip's -@, you can use shell globs to select files directly on the zip command line e.g. zip files.zip file* or zip images.zip *.jpg. Also, when using a list of filenames from a file, you can use redirection instead of invoking a separate cat process, zip files.zip -@ < zip.lst May 25, 2016 at 0:28
  • How can you make this work with nested directories alongside files? May 9, 2018 at 14:46
  • @BallpointBen I'd use find instead of ls May 9, 2018 at 18:56
  • For anyone else on a Mac that wound up here courtesy of search engine indexing, I discovered that the -@ option syntax works fine (as of 10.14.6), even though the man page includes the language about [not on MacOS]. Feb 10, 2020 at 19:47
6

the most simple command with max compression level

zip -9 -r filename.zip /path/to/dir singlefile.jpg 

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .