Warum funktioniert "zip" in einer for-Schleife, wenn die Datei vorhanden ist, aber nicht, wenn dies nicht der Fall ist?

7

Ich habe ein Verzeichnis, das mehrere Unterverzeichnisse enthält. Es gibt eine Frage zum Komprimieren der Dateien , die eine Antwort enthält, die ich ganz leicht an meine Bedürfnisse angepasst habe.

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

Ich stoße jedoch auf ein bizarres Problem. Bei den ersten Ordnern, zips/<name>.zipdie nicht vorhanden sind, wird folgende Fehlermeldung angezeigt:

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

aber wenn ich nur echodie zip-Anweisungen habe:

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

Führen Sie dann den Befehl echoed ( zip zips/2014-10.zip 2014-10/*.csv) aus, er funktioniert einwandfrei und komprimiert den Ordner. Dann ist der spaßige Teil daran ist , dass nachfolgende Durchläufe des ursprünglichen Befehl wird tatsächlich Ordner Reißverschluss, der nicht das erste Mal nicht funktioniert!

So testen Sie dieses Verhalten selbst:

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

Sie werden sehen, dass das Echo diese Anweisungen druckt:

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

Der eigentliche Zip-Befehl sagt Ihnen jedoch:

        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%)

Es wird also tatsächlich die Zip-Datei mit dem .csvs aktualisiert, in dem die Zip-Datei vorhanden ist, aber nicht, wenn die Zip-Datei erstellt wird. Und wenn Sie einen der Zip-Befehle kopieren:

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

Führen Sie dann den Reißverschluss erneut aus:

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

Sie werden sehen, dass es für 2016-02und aktualisiert wird 2016-03. Hier ist meine Ausgabe von 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

Auch (un) überraschend funktioniert dies einwandfrei:

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

Was mache ich hier falsch? (Beachten Sie, dass ich zsh anstelle von bash verwende, wenn das einen Unterschied macht.)

Wayne Werner
quelle
Könnten Sie den genauen "Echo-Befehl" anzeigen, den er ausgegeben hat und den Sie erfolgreich ausgeführt haben?
JigglyNaga
@ JigglyNaga zip zips/2014-10.zip 2014-10/*.csv(auch zum Q hinzugefügt)
Wayne Werner
1
Der Fehler besteht darin, dass keine *cvsDatei vorhanden ist und nicht fehlt .zip. Können Sie uns ein minimales Beispiel für die von Ihnen verwendete Verzeichnis- / Dateistruktur geben, damit wir sie neu erstellen und den Fehler testen können?
Terdon
@terdon done - Schritte zum Repro zusammen mit der Ausgabe des Baums. Es scheint mir ein sehr seltsames Verhalten zu sein. Wenn es nur darum ging, dass sich der Befehl irgendwie im falschen Verzeichnis befand, würde ich erwarten, dass er für alle Zip-Dateien fehlschlägt , aber für diejenigen, die manuell erstellt wurden, funktioniert er ...
Wayne Werner
Problemumgehung hinzugefügt ... aber warum funktioniert das Original nicht?
Wayne Werner

Antworten:

14

Erweiterung durch die Schale

Die Anführungszeichen "$i*.csv"machen den Unterschied. Mit den Anführungszeichen erweitert die Shell diese Zeichenfolge auf "2014-11 / *. CSV". Diese genaue Datei existiert nicht und zipmeldet einen Fehler. Ohne Anführungszeichen wird das *ebenfalls erweitert (über Dateinamenerweiterung / "Globbing"), und der resultierende zipBefehl ist eine vollständige Liste der übereinstimmenden Dateien, jeweils als separates Argument. Sie können das zweite Verhalten innerhalb der forSchleife erhalten mit:

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

Erweiterung per Reißverschluss

zipkann auch Platzhalter für sich selbst erweitern, jedoch nicht in allen Situationen. Aus dem Zip-Handbuch :

Das Zip- Programm kann den gleichen Abgleich für Namen durchführen, die sich im zu ändernden Zip- Archiv befinden, oder im Fall der Optionen -x (ausschließen) oder -i (einschließen) in der Liste der zu bearbeitenden Dateien mithilfe von Backslashes oder Anführungszeichen, um die Shell anzuweisen, die Namenserweiterung nicht durchzuführen.

Der ursprüngliche Befehl funktioniert bei nachfolgenden Versuchen, nachdem Sie erfolgreich ein Archiv erstellt haben, da zipversucht wird, die Platzhalter mit dem Inhalt des vorhandenen Archivs abzugleichen. Sie sind dort vorhanden und befinden sich noch im Dateisystem, sodass sie mit gemeldet werden updating:.

Um zipdie Platzhalter beim Erstellen des Archivs zu verarbeiten, verwenden Sie die -rOption (recurse), um in das angeforderte Verzeichnis zurückzukehren, und -i(include), um es auf Dateien zu beschränken, die dem Muster entsprechen:

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