7

I have a directory that contains several sub-directories. There is a question about zipping the files that contains an answer that I ever-so-slightly modified for my needs.

for i in */; do zip "zips/${i%/}.zip" "$i*.csv"; done

However, I run into a bizarre problem. For the first set of folders, where zips/<name>.zip does not exist, I get this error:

zip error: Nothing to do! (zips/2014-10.zip)
        zip warning: name not matched: 2014-11/*.csv

however when I just echo the zip statements:

for i in */; do echo zip "zips/${i%/}.zip" "$i*.csv"; done

Then run the echoed command (zip zips/2014-10.zip 2014-10/*.csv), it works fine and zips up the folder. Then the fun part about that is that subsequent runs of the original command will actually zip up folders that didn't work the first time!

To test this behavior yourself:

cd /tmp
mkdir -p 2016-01 2016-02 2016-03 zips
for i in 2*/; do touch "$i"/one.csv; done
for i in 2*/; do touch "$i"/two.csv; done
zip zips/2016-03.zip 2016-03/*.csv
for i in 2*/; do echo zip "zips/${i%/}.zip" "$i*.csv"; done
for i in 2*/; do zip "zips/${i%/}.zip" "$i*.csv"; done

You'll see that the echo prints these statements:

zip zips/2016-01.zip 2016-01/*.csv
zip zips/2016-02.zip 2016-02/*.csv
zip zips/2016-03.zip 2016-03/*.csv

However, the actual zip command will tell you:

        zip warning: name not matched: 2016-01/*.csv

zip error: Nothing to do! (zips/2016-01.zip)
        zip warning: name not matched: 2016-02/*.csv

zip error: Nothing to do! (zips/2016-02.zip)
updating: 2016-03/one.csv (stored 0%)
updating: 2016-03/two.csv (stored 0%)

So it's actually updating the zip file with the .csvs where the zip file exists, but not when the zip file is created. And if you copy one of the zip commands:

$ zip zips/2016-02.zip 2016-02/*.csv
adding: 2016-02/one.csv (stored 0%)
adding: 2016-02/two.csv (stored 0%)

Then re-run the zip-all-the-things:

for i in 2*/; do zip "zips/${i%/}.zip" "$i*.csv"; done

You'll see that it updates for 2016-02 and 2016-03. Here's my output of tree:

.
├── 2016-01
│   ├── one.csv
│   └── two.csv
├── 2016-02
│   ├── one.csv
│   └── two.csv
├── 2016-03
│   ├── one.csv
│   └── two.csv
└── zips
    ├── 2016-02.zip
    └── 2016-03.zip

Also, (un)surprisingly, this works just fine:

zsh -c "$(for i in 2*/; do echo zip "zips/${i%/}.zip" "$i*.csv"; done)"

What am I doing wrong here? (note, I am using zsh instead of bash, if that makes any difference)

  • Could you show the exact "echoed command" that it output and you successfully ran? – JigglyNaga Aug 17 '16 at 12:34
  • @JigglyNaga zip zips/2014-10.zip 2014-10/*.csv (also added to the Q) – Wayne Werner Aug 17 '16 at 12:51
  • 1
    The error is about no *cvs file existing, not about missing .zip. Could you give us a minimal example of the directory/file structure you're using so we can recreate it and test the error? – terdon Aug 17 '16 at 13:05
  • @terdon done - steps to repro, along with the output of tree. It seems like a very strange behavior to me. If it were simply a matter of somehow the command was in the wrong directory, I'd expect that it fail for all of the zip files, but it does work for the ones that are manually created... – Wayne Werner Aug 17 '16 at 13:18
  • Added a workaround... but why doesn't the original work? – Wayne Werner Aug 17 '16 at 13:20
14

Expansion by the shell

The quotes around "$i*.csv" make the difference. With the quotes, the shell expands that string to "2014-11/*.csv". That exact file doesn't exist, and zip reports an error. Without quotes, the * also expands (via filename expansion/"globbing"), and the resulting zip command is a complete list of matching files, each as a separate argument. You can get the second behaviour, inside the for loop, with:

for i in */ ; do zip "zips/${i%/}.zip" "$i"*.csv ; done

Expansion by zip

zip can also expand wildcards for itself, but not in all situations. From the zip manual:

The zip program can do the same matching on names that are in the zip archive being modified or, in the case of the -x (exclude) or -i (include) options, on the list of files to be operated on, by using backslashes or quotes to tell the shell not to do the name expansion.

The original command works on subsequent attempts, after you've successfully created an archive, because zip tries to match the wildcards against the contents of the existing archive. They exist there, and still exist on the filesystem, so they're reported with updating:.

To get zip to handle the wildcards when creating the archive, use the -r (recurse) option to recurse into the requested directory, and -i (include) to limit it to files matching the pattern:

 for i in */ ; do zip -r "zips/${i%/}.zip" "$i" -i '*.csv' ; done

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.