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 .csv
s 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)
Best Answer
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, andzip
reports an error. Without quotes, the*
also expands (via filename expansion/"globbing"), and the resultingzip
command is a complete list of matching files, each as a separate argument. You can get the second behaviour, inside thefor
loop, with:Expansion by zip
zip
can also expand wildcards for itself, but not in all situations. From the zip manual: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 withupdating:
.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: