Wie kopiere ich einen Ordner mit cp rekursiv und idempotent?

46

Wenn ich benutze

cp -R inputFolder outputFolder

Das Ergebnis ist kontextabhängig :

  1. Ist outputFolderdies nicht der Fall, wird es erstellt und der Pfad des geklonten Ordners wird angezeigt outputFolder.
  2. Wenn outputFoldervorhanden, wird der erstellte Klon erstelltoutputFolder/inputFolder

Das ist schrecklich , weil ich ein Installationsskript erstellen möchte, und wenn der Benutzer es versehentlich zweimal ausführt, hat er es outputFolderbeim ersten Mal erstellt, und beim zweiten Mal wird das gesamte Material in neu erstellt outputFolder/inputFolder.

  • Ich möchte immer das erste Verhalten: einen Klon neben dem Original erstellen (als Geschwister).
  • Ich möchte cpportabel sein (zB MINGW wird nicht rsyncausgeliefert)
  • Ich habe es überprüft, cp -R --parentsaber dies stellt den Pfad bis zum Ende des Verzeichnisbaums wieder her (der Klon wird also nicht outputFolderaber sein some/path/outputFolder).
  • --remove-destinationoder --updatefalls 2 nichts ändert, werden noch dinge in kopiertoutputFolder/inputFolder

Gibt es eine Möglichkeit, dies zu tun, ohne vorher zu prüfen, outputFolderob der Ordner existiert (wenn er nicht existiert, dann ...) oder zu verwenden rm -rf outputFolder?

Wie sieht die vereinbarte, portable UNIX-Methode dafür aus?

jakub.g
quelle
2
Einige dieser Optionen sind nicht POSIX-fähig, daher sind die von Ihnen getesteten Optionen nicht sehr portabel. Wenn Sie mit einem kleinen Mangel an Portabilität leben können, warum nicht verwenden rsync?
Muru
1
Wenn Sie diese Funktion nicht benötigen, cpist die Weiterleitung zwischen zwei tarBefehlen eine gute und zuverlässige Methode zum Kopieren von Bäumen.
R ..
@R .. kannst du ein Beispiel geben? @muru Ich möchte, dass dies in MINGW funktioniert, das nicht rsyncausgeliefert wurde.
jakub.g
Funktioniert mit GNU tar tar -C input -cf - . | tar -C output -xf -. -CÄndert das Arbeitsverzeichnis, ist aber keine Standardoption. Wenn dies nicht unterstützt wird, müssen Sie zunächst die beiden Teer in den richtigen Arbeitsverzeichnissen ausführen. Beispiel: ( cd input && tar -cf - . ) | ( cd output && tar -xf - )Wenn Sie jedoch mit Windows arbeiten, würde ich nur die Antwort von Roaima verwenden.
R ..

Antworten:

54

Verwenden Sie dies stattdessen:

cp -R inputFolder/. outputFolder

Dies funktioniert genauso wie zum Beispiel cp -R aaa/bbb ccc: Wenn ccces keine gibt, wird sie als Kopie von bbbund ihrem Inhalt erstellt. Ist dies jedoch cccbereits der Fall, ccc/bbbwird eine Kopie von bbbund deren Inhalt erstellt.

Für fast jeden Fall bbbergibt sich das unerwünschte Verhalten, das Sie in Ihrer Frage notiert haben. In dieser konkreten Situation ist das bbbjedoch gerecht ., so wie aaa/bbbes wirklich ist aaa/., was wiederum wirklich gerecht ist, aaaaber unter einem anderen Namen. Wir haben also diese beiden Szenarien:

  1. ccc ist nicht vorhanden:

    Der Befehl cp -R aaa/. cccbedeutet " cccden Inhalt von aaa/.in erstellen und kopieren ccc/., dh kopieren aaain ccc.

  2. ccc existiert:

    Der Befehl cp -R aaa/. cccbedeutet "kopiere den Inhalt von aaa/.in ccc/., dh kopiere aaain ccc.

Roaima
quelle
3
Huh, das ist neu für mich, aber es scheint zu funktionieren! Ist dies irgendwo dokumentiert?
Ilmari Karonen,
1
Ich habe das getestet inputFolder/.statt inputFolderund in der Tat funktioniert es für mich auf Windows / Git Bash. (Es geht aber nicht mit nur einem abschließenden /, ich brauche auch den Punkt nach dem Schrägstrich). Ich habe +1 gegeben, da es interessant ist, obwohl es wahrscheinlich ein bisschen zu schwierig ist, es in öffentlichem Code zu verwenden :)
jakub.g
@IlmariJaronen es muss nicht explizit dokumentiert werden. Folgen Sie der Erklärung und Sie werden sehen, wie es einfach "den Regeln folgt".
Roaima
18

Kopieren Sie nicht den Ordner, sondern nur den Inhalt:

## Create the target directory. The -p suppresses error messages
## if the directory already exists
mkdir -p outputFolder

## Copy the contents recursively, this will not recreate the parent
cp -R inputfolder/* outputfolder/

Auf diese Weise stellen Sie beide sicher, dass das Zielverzeichnis beim ersten Ausführen des Skripts erstellt wird, und vermeiden das Problem, wenn Sie es ein zweites Mal ausführen.

Chris Down weist sehr richtig darauf hin, dass in bash Dateien übersprungen werden, deren Name mit a beginnt .. Um dies zu vermeiden, können Sie ausführen, shopt -s dotglobbevor Sie den obigen Befehl ausführen .

Sowohl -pfürmkdir als auch -Rfürcp werden von POSIX definiert, daher sollte dies perfekt portierbar sein.

terdon
quelle
6
Hinweis: Hiermit werden keine Dateien kopiert, die mit beginnen .. In Bash können Sie dafür verwenden dotglob.
Chris Down
2
Beachten Sie, dass diese Methode wahrscheinlich fehlschlägt, wenn die Anzahl der Verzeichniseinträge im Eingabeordner groß ist, da die Glob-Erweiterung die maximale Befehlszeilenlänge überschreiten kann.
Tim Cutts
11

Versuchen Sie die -TOption zu cp. Dies existiert in GNU Coreutils cpVersion 8.22; es kann außerhalb davon nicht tragbar sein.

Tom Hunt
quelle
1
Ja, es scheint genau das zu sein, was ich wollte: cp -T( cp --no-target-directory). unix.stackexchange.com/questions/94831/… Leider ist meine Version von cpviel älter.
jakub.g
1
+1 Habe das erst heute benutzt. -uist eine großartige Option, um es faul zu machen.
PSkocik,
4

Sie könnten auch verwenden rsync:

rsync -uav inputFolder/ outputFolder/

(Beachten Sie die Schrägstriche, besonders nach dem ersten)

Ned64
quelle
0

Meiner Meinung nach gibt es nichts Einfacheres und Tragbareres als rm -rf outputFolder. Also bleibe ich immer dabei. Ich verstehe, dass Ihre Frage anders ist, aber ich denke, dass dies die beste Vorgehensweise ist.

Dashohoxha
quelle
Dies schlägt fehl, wenn auf dem Ziel bestimmte Berechtigungen oder Eigentumsrechte vorhanden waren, die beibehalten werden mussten. Oder wenn es ein Symlink war.
Roaima
1
Die Frage möchte, dass das Ergebnis cpgleich ist, sowohl wenn das Verzeichnis nicht existiert als auch wenn es existiert. Also, wovon redest du?
Dashohoxha
Der cpvom OP gegebene Befehl behält keine Berechtigungen (oder Zeitstempel) bei.
Roaima
0

Sie können die -tBefehlsoption verwenden cpals:

cp -R inputFolder -t outputFolder

Wenn der Zielordner nicht existiert, wird ein Fehler ausgegeben:

cp: failed to access ‘outputFolder’: No such file or directory

Der obige Befehl kopiert inputFolderzusammen mit dem Inhalt (und nicht nur mit dem Inhalt darin).

Wenn Sie nur den Inhalt kopieren möchten, wird inputFolderes etwas knifflig (da Sie vorsichtig sein müssen, wenn Sie Shell-Globbing verwenden, während Sie Sternchen * verwenden).

cp -R  -t outputFolder/ -- inputFolder/*

Wenn der Zielordner nicht existiert, wird ein Fehler ausgegeben:

cp: failed to access ‘outputFolder’: No such file or directory

arbeitet mit cp (GNU coreutils) 8.23

Edward Torvalds
quelle