2013-03-16 16 views
5

In den Verzeichnissen ~/temp/a/foo/ und ~/temp/b/foo foo/ Ich habe einige Dateien bar1 genannt, bar2, bar bar1, bar bar2 usw.doppelte Anführungszeichen vs Stern Dateinamen Expansion in Bash

Ich versuche, eine Linie von Bash zu schreiben, dass Kopien all diese Dateien in einem Verzeichnis mit "foo" als letzten Teil des Namens zum Ordner über dem jeweiligen "foo" -Ordner.

Solange es keine Leerzeichen in den Dateinamen sind, ist dies eine einfache Aufgabe, aber die beiden folgenden Befehle fehlschlagen, wenn mit dem foo foo Verzeichnis zu tun:

for dir in `find . -type d -name '*foo'` ; do cp $dir/* "$(echo $dir|sed 's_foo__g')" ; done 

(Der cp Befehl schlägt fehl, die sehen letzte foo von "foo foo" als Teil des gleichen Verzeichnisnamen.)

for dir in `find . -type d -name '*foo'` ; do cp "$dir/*" "$(echo $dir|sed 's_foo__g')" ; done 

("$dir/*" nicht ausgeklappt.)

Versuche, $dir/* durch "$(echo $dir/*)" zu ersetzen, waren noch weniger erfolgreich.

Gibt es eine einfache Möglichkeit, $dir/* zu erweitern, so dass cp versteht?

+0

Siehe http://mywiki.wooledge.org/DontReadLinesWithFor –

Antwort

4

Nicht nur ist eine for Schleife falsch - sed ist auch nicht das richtige Werkzeug für diesen Job.

while IFS= read -r -d '' dir; do 
    cp "$dir" "${dir/foo/}" 
done < <(find . -type d -name '*foo' -print0) 

Mit -print0 auf dem find (und IFS= read -r -d '') stellt sicher, dass die Dateinamen mit Zeilenumbrüche werden Sie nicht vermasseln. Das < <(...) Konstrukt stellt sicher, dass, wenn das Innere Ihrer Schleife Variablen setzt, den Verzeichniszustand ändert oder ähnliche Dinge tut, diese Änderungen bestehen bleiben (die rechte Seite einer Pipeline befindet sich in einer Subshell in bash, also piping) in eine while-Schleife würde bedeuten, dass alle Änderungen an dem Shell-Zustand, der innerhalb dieser while-Schleife gemacht wird, an seinem Ausgang verworfen würden, andernfalls).

Die Verwendung von ${dir/foo/} ist erheblich effizienter als das Aufrufen von sed, da es den String-Ersatz innerhalb von Bash tut.

+0

Sind Sie sicher, dass dort 'IFS =' benötigt wird? Toller Post übrigens! – janos

+0

Trotz des Vorschlags von Janos, diese Antwort als Lösung zu akzeptieren, stellt sich heraus, dass der Code tatsächlich keine Dateien verschiebt. Bash 4.2.24 beschwert sich, dass cp nicht stattzugeben vermag. Ich bin nicht 100% sicher, was die Zeile 'cp" $ dir "" $ {dir/foo /} "' tut. Wird wirklich versucht, Dateien in ein bestehendes Verzeichnis oberhalb des 'foo'-Verzeichnisses zu kopieren? – uvett

+0

Sollte "Datei lesen" nicht "gelesen ..." lauten? –

0

Benennen Sie ~/temp/b/foo foo/ in etwas ohne Leerzeichen um, z. ~/temp/b/foo_foo/ und tun Sie, was Sie wieder versuchen wollten. Danach können Sie es mit dem Platz zurück zum Original umbenennen, wenn Sie wirklich müssen. Übrigens. Ich benutze niemals Dateinamen, die Leerzeichen enthalten, um Komplikationen wie die, mit der Sie jetzt konfrontiert sind, zu vermeiden.

+0

Ich habe versucht, ein Problem zu lösen, das Dutzende von Ordnern und Hunderte von Dateien mit hässlichen Namen betrifft. Das Umbenennen scheint eine nicht optimale Option zu sein, zumal ich die Ordnernamen später wiederherstellen müsste. – uvett

2

Das Problem hier ist nicht mit cp, aber mit for, weil es standardmäßig die Ausgabe Ihrer Subshell durch Wörter, nicht durch Verzeichnisnamen teilt.

Ein fauler Abhilfe ist while stattdessen zu verwenden und die Liste der Verzeichnisse Zeile für Zeile wie folgt zu verarbeiten:

find . -type d -name '*foo' | while read dir; do cp "$dir"/* "$(echo $dir | sed 's_foo__g')" ; done 

Dieses Ihr Problem mit Leerzeichen beheben sollte, aber dies ist keineswegs eine narrensichere Lösung.

Siehe Charles Duffys Antwort für eine genauere Lösung.

+0

Fast richtig, aber Sie sollten wirklich den Inhalt der Pipeline NUL-begrenzen - andernfalls kann ein in böswilliger Absicht erstellter Dateiname, der einen Zeilenumbruch enthält, jede beliebige Datei kopieren. –

+0

... übrigens, es ist auch nicht korrekt, dass "dir" niemals Leerzeichen als Artefakt dessen enthält, wie "für" funktioniert; word-splitting ist _nicht_ ein Teil von fors Semantik, und wenn 'IFS' so eingestellt ist, dass kein Whitespace enthalten ist, enthält die String-Split-Ausgabe _can_ Leerzeichen. Allerdings ist es aus anderen Gründen falsch, sich auf das String-Splitting zu verlassen (zum Beispiel, weil die Glob-Expansion zur gleichen Zeit stattfindet). –

+0

Danke, das scheint für alle meine realen Kopierprobleme zu funktionieren. Dass es an meinem vereinfachten Beispiel tatsächlich scheitert, ist ein lustiges Detail. (sed löscht beide 'foo's im Ordnernamen' foo foo'). – uvett