Bash: Kopieren Sie benannte Dateien rekursiv, wobei die Ordnerstruktur erhalten bleibt

103

Ich hoffte:

cp -R src/prog.js images/icon.jpg /tmp/package

würde eine symmetrische Struktur im Zielverzeichnis ergeben:

/tmp
|
+-- package
    |
    +-- src
    |   |
    |   +-- prog.js
    |
    +-- images
        |
        +-- icon.jpg

Stattdessen werden beide Dateien in / tmp / package kopiert. Eine flache Kopie. (Dies ist unter OSX).

Gibt es eine einfache Bash-Funktion, mit der ich alle Dateien, einschließlich der durch Platzhalter angegebenen Dateien (z. B. src / *. Js), an ihren rechtmäßigen Platz im Zielverzeichnis kopieren kann? Ein bisschen wie "für jede Datei ausführen mkdir -p $(dirname "$file"); cp "$file" $(dirname "$file")", aber vielleicht ein einzelner Befehl.

Dies ist ein relevanter Thread, was darauf hindeutet, dass dies nicht möglich ist. Die Lösung des Autors ist für mich jedoch nicht so nützlich, da ich einfach eine Liste von Dateien, Platzhalter oder nicht, bereitstellen und alle in das Zielverzeichnis kopieren lassen möchte. IIRC MS-DOS xcopy macht dies, aber es scheint kein Äquivalent für cp zu geben.

Mahemoff
quelle

Antworten:

153

Haben Sie versucht, die Option --parents zu verwenden? Ich weiß nicht, ob OS X das unterstützt, aber das funktioniert unter Linux.

cp --parents src/prog.js images/icon.jpg /tmp/package

Wenn dies unter OS X nicht funktioniert, versuchen Sie es

rsync -R src/prog.js images/icon.jpg /tmp/package

wie aif vorgeschlagen.

ustun
quelle
4
Vielen Dank. Es stellt sich heraus, dass "cp --parents" auf einem Mac nicht möglich ist, aber es ist schön, die Flagge für andere Unixen zu kennen. rsync -R ist die einfachste tragbare Lösung für dieses Problem.
Mahemoff
1
Ich habe dieses wegen seiner Eleganz / Einprägsamkeit akzeptiert, aber gerade festgestellt, dass es nicht ganze Verzeichnisse kopiert (zumindest unter OSX), während das unten stehende Teer dies tut.
Mahemoff
cp --parentsist eine illegale Option in OSX (BSD cp), aber gcp(GNU cp) funktioniert einwandfrei. Wenn es noch nicht in Ihrem System ist, verwenden Sie brew install coreutils. U wird viele Utils mit g-Präfix haben.
Kyb
@mahemoff cp -R --parentsund rsync -rRkopiert sowohl Dateien als auch Verzeichnisse relativ.
Vortico
22

Einweg:

tar cf - <files> | (cd /dest; tar xf -)
Randy Proctor
quelle
Oh, ich mag das viel besser als meine Antwort.
EMPraptor
4
Sie können auch die -COption verwenden, um das chdir für Sie zu erledigen - tar cf - _files_ | tar -C /dest xf -oder so ähnlich.
D. Shawley
danke, das ist prägnant, obwohl ich der Einfachheit halber rsync bevorzuge.
Mahemoff
1
Toll! Weiß jemand, wie man daraus einen Shell-Befehl macht? Es sollte N Eingaben akzeptieren, die ersten N-1 sind die zu kopierenden Dateien und die letzte ist der Zielordner.
Arod
@arod $ {! #} ist der letzte Parameter und verwendet diesen, um die vorhergehenden Argumente zu erhalten stackoverflow.com/questions/1215538/… . Wenn Sie den Befehl schreiben, verlinken Sie bitte hier auf das Wesentliche.
Mahemoff
18

Wenn Sie altmodisch sind, verwenden Sie alternativ cpio:

cd /source;
find . -print | cpio -pvdmB /target

Natürlich können Sie die Dateiliste nach Herzenslust filtern.

Die Option '-p' ist für den Durchgangsmodus vorgesehen (im Gegensatz zu '-i' für die Eingabe oder '-o' für die Ausgabe). Das '-v' ist ausführlich (listet die Dateien auf, während sie verarbeitet werden). Das '-m' behält die Änderungszeiten bei. Das '-B' bedeutet 'große Blöcke' verwenden (wobei große Blöcke 5120 Bytes anstelle von 512 Bytes sind); es ist möglich, dass es heutzutage keine Wirkung hat.

Jonathan Leffler
quelle
1
Es ist besser, -print0mit der Kombination von --nullOptionen zu verwenden, damit es nicht mit Sonderzeichen und dergleichen find . -print0 | cpio -pvdmB --null /target
bricht
Nennen wir es Old School, nennen wir es tragbar, nennen wir es wie auch immer, aber ich vertraue definitiv cpioauf diese Aufgabe. Ich bin damit einverstanden, dass die Optionen -print0und -nullverwendet werden sollten, andernfalls wird Ihnen irgendwann jemand Ordner mit 'Sonderzeichen' (Leerzeichen, höchstwahrscheinlich) geben und etwas wird passieren. Nicht, dass ich aus persönlicher Erfahrung spreche, aber Sie könnten versuchen, eine Reihe von Dateien zu sichern und nur die Hälfte davon zu sichern, da Leerzeichen in Dateinamen enthalten sind. (Okay, ich spreche aus persönlicher Erfahrung.)
bballdave025
@ bballdave025: Sie haben nur dann Probleme mit, cpiowie gezeigt, wenn Dateinamen Zeilenumbrüche enthalten. Normalerweise wird eine Fehlermeldung angezeigt, dass zwei (oder mehr) Dateinamen nicht für jede neue Zeile in einem Dateinamen gefunden werden. (Manchmal erhalten Sie möglicherweise weniger Nachrichten - dies erfordert jedoch erhebliche Sorgfalt bei der Erstellung des Testfalls.) Als ich benutzte cpio, gab es keine --nullOption; Double-Dash-Optionen waren nicht Teil der SVR4-Optionsnotationen, und das Konzept von -print0war auch nicht vorhanden find. Aber das ist lange her (zum Beispiel Mitte der 90er Jahre, bevor Linux die Dominanz erlangte).
Jonathan Leffler
Danke für die Details, @Jonathan_Leffler. Ich liebe es, hier etwas zu lernen. Jetzt versuche ich mich zu erinnern, welchen Fehler beim rekursiven Kopieren ich gemacht habe, der mir Probleme mit Leerzeichen verursacht hat - es gibt viele Möglichkeiten, wie ich diesen Fehler hätte machen können
bballdave025
@ bballdave025 - Eine Möglichkeit ist die Verwendung xargsin der Mischung - sie wird im Leerraum aufgeteilt - Leerzeichen, Tabulatoren, Zeilenumbrüche. OTOH, ich bin mir nicht sicher, wie oder warum du das machen würdest. Das GNU- cpioManul im Ausgabemodus enthält ungefähr einen Dateinamen pro Zeile. Das SVR4-Benutzerhandbuch (gedruckt 1990) ist vage: cpio -o( Kopiermodus ) liest die Standardeingabe, um eine Liste von Pfadnamen zu erhalten, und kopiert diese Dateien zusammen mit dem Pfadnamen und den Statusinformationen in die Standardausgabe. Es besteht daher die Möglichkeit, dass Namen an Leerzeichen gebrochen wurden.
Jonathan Leffler
16

Die Option -R von rsync macht das, was Sie erwarten. Es ist ein sehr funktionsreicher Dateikopierer. Beispielsweise:

$ rsync -Rv src/prog.js images/icon.jpg /tmp/package/
images/
images/icon.jpg
src/
src/prog.js

sent 197 bytes  received 76 bytes  546.00 bytes/sec
total size is 0  speedup is 0.00

Beispielergebnisse:

$ find /tmp/package
/tmp/package
/tmp/package/images
/tmp/package/images/icon.jpg
/tmp/package/src
/tmp/package/src/prog.js
Ryan Bright
quelle
1

Versuchen...

for f in src/*.js; do cp $f /tmp/package/$f; done

Also für das, was du ursprünglich gemacht hast ...

for f in `echo "src/prog.js images/icon.jpg"`; do cp $f /tmp/package/$f; done

oder

v="src/prog.js images/icon.jpg"; for f in $v; do cp $f /tmp/package/$f; done
EMPraptor
quelle
Du brauchst nicht echooder $vhier. Auch diese Methode schlägt fehl, wenn das entsprechende Verzeichnis im Ziel nicht vorhanden ist.
Weijun Zhou