Comment copier un dossier récursivement de manière idempotente en utilisant cp?

46

Quand j'utilise

cp -R inputFolder outputFolder

le résultat dépend du contexte :

  1. s'il outputFoldern'existe pas, il sera créé et le chemin du dossier cloné sera outputFolder.
  2. s'il outputFolderexiste, le clone créé seraoutputFolder/inputFolder

C'est horrible , car je veux créer un script d'installation. Si l'utilisateur l'exécute deux fois par erreur, il l'aura outputFoldercréé la première fois. À la deuxième exécution, tous les éléments seront créés à nouveau outputFolder/inputFolder.

  • Je veux toujours le premier comportement: créer un clone à côté de l'original (en tant que frère ou soeur).
  • Je veux utiliser cppour être portable (par exemple, MINGW n'a pas été rsyncexpédié)
  • J'ai vérifié cp -R --parentsmais cela recrée le chemin jusqu'au bout de l'arborescence (donc le clone ne sera pas outputFoldermais some/path/outputFolder)
  • --remove-destinationou si le --updatecas 2 ne change rien, les choses sont encore copiées dansoutputFolder/inputFolder

Est-il possible de le faire sans d'abord vérifier l'existence du outputFolder(si le dossier n'existe pas alors ...) ou en utilisant rm -rf outputFolder?

Quel est le moyen UNIX convenu et portable de faire cela?

jakub.g
la source
2
Certaines de ces options ne sont pas POSIX, donc celles que vous avez essayées ne sont pas très portables. Si vous pouvez vivre avec un petit manque de portabilité, pourquoi ne pas utiliser rsync?
Muru
1
Si vous n'avez pas besoin de l'utiliser cp, la canalisation entre deux tarcommandes est un moyen fiable et agréable de copier des arbres.
R ..
@R .. pouvez-vous donner un exemple? @muru J'aimerais que cela fonctionne dans MINGW qui n'a pas encore été rsyncexpédié.
jakub.g
Avec GNU tar, ça tar -C input -cf - . | tar -C output -xf -marche. -Cmodifie le répertoire de travail, mais ce n’est pas une option standard, donc s’il n’est pas pris en charge, vous devez commencer par exécuter les deux ( cd input && tar -cf - . ) | ( cd output && tar -xf - )fichiers tar dans les répertoires de travail appropriés. Par exemple, si vous utilisez Windows, je n’utiliserai que la réponse de roaima.
R ..

Réponses:

54

Utilisez ceci à la place:

cp -R inputFolder/. outputFolder

Cela fonctionne exactement de la même manière que, par exemple cp -R aaa/bbb ccc: si ce cccn’existe pas, il est créé en tant que copie bbbet son contenu; mais s'il cccexiste déjà, il ccc/bbbest créé en tant que copie bbbet son contenu.

Dans presque tous les cas, bbbcela donne le comportement indésirable que vous avez noté dans votre question. Cependant, dans cette situation spécifique, le bbbest juste ., donc aaa/bbbvraiment aaa/., ce qui à son tour est vraiment juste aaamais sous un autre nom. Nous avons donc ces deux scénarios:

  1. ccc n'existe pas:

    La commande cp -R aaa/. cccsignifie "créer cccet copier le contenu de aaa/.dans ccc/., c.- aaaà-d ccc. Copier dans .

  2. ccc existe:

    La commande cp -R aaa/. cccsignifie "copier le contenu de aaa/.dans ccc/., c.- aaaà-d ccc. Copier dans .

Roaima
la source
3
Euh, c'est un nouveau pour moi, mais cela semble fonctionner! Est-ce documenté n'importe où?
Ilmari Karonen
1
J'ai testé le inputFolder/.au lieu de inputFolderet en effet cela fonctionne pour moi sur Windows / Git Bash. (Cela ne fonctionne pas cependant avec juste une fin /, j'ai aussi besoin du point après la barre oblique). J'ai donné +1 car c'est intéressant, même si c'est probablement un peu trop compliqué de l'utiliser en code public :)
jakub.g
@IlmariJaronen il n'a pas besoin d'être documenté explicitement. Suivez l'explication et vous verrez comment il "suit les règles".
Roaima
18

Ne copiez pas le dossier, copiez seulement le contenu:

## 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/

De cette façon, vous vous assurez tous les deux que le répertoire cible est créé lors de la première exécution du script et évitez le problème lorsque vous l'exécutez une seconde fois.

Chris Down fait remarquer à juste titre que lors de bash, les fichiers dont le nom commence par un seront ignorés .. Pour éviter cela, vous pouvez exécuter shopt -s dotglobavant d’exécuter la commande ci-dessus.

Les deux -ppourmkdir et -Rpourcp sont définies par POSIX ce qui devrait être parfaitement portable.

terdon
la source
6
Remarque: cela ne copiera pas les fichiers commençant par .. En bash, vous pouvez utiliser dotglobpour cela.
Chris Down
2
Notez que cette méthode est susceptible d'échouer si le nombre d'entrées de répertoire dans inputfolder est important, car l'extension globale risque de dépasser la longueur maximale de la ligne de commande.
Tim Cutts
11

Essayez l' -Toption pour cp. Cela existe dans la cpversion 8.22 de GNU coreutils ; il ne peut pas être portable en dehors de cela.

Tom Hunt
la source
1
Oui, il semblerait que ce soit exactement ce que je voulais: cp -T( cp --no-target-directory). unix.stackexchange.com/questions/94831/… Malheureusement, ma version de cpest beaucoup plus ancienne.
jakub.g
1
+1 utilisé cela juste aujourd'hui. -uest une excellente option à ajouter pour le rendre paresseux.
PSkocik
4

Vous pouvez aussi utiliser rsync:

rsync -uav inputFolder/ outputFolder/

(notez les barres obliques, surtout après le premier)

Ned64
la source
0

À mon avis, rien de plus simple et portable rm -rf outputFolder. Donc, je m'en tiens toujours à ça. Je comprends que votre question est différente, mais je pense que c'est la meilleure pratique.

dashohoxha
la source
Cela échoue si des autorisations ou des propriétés spécifiques sur la cible doivent être conservées. Ou si c'était un lien symbolique.
Roaima
1
La question veut que le résultat de cpsoit le même, à la fois lorsque le répertoire n'existe pas et quand il existe. Alors qu'est-ce que tu racontes?
Dashohoxha
La cpcommande donnée par l'OP ne conserve pas les autorisations (ou les horodatages).
roaima
0

Vous pouvez utiliser l' -toption de cpcommande comme:

cp -R inputFolder -t outputFolder

maintenant, si le dossier cible n'existe pas, une erreur sera générée:

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

Remarque La commande ci-dessus copiera inputFolderavec son contenu (et pas seulement le contenu qu'il contient)

si vous ne voulez copier que le contenu, inputFoldercela devient un peu compliqué (car vous devez faire attention lorsque vous utilisez un shell globbing tout en utilisant un astérisque *)

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

maintenant, si le dossier cible n'existe pas, une erreur sera générée:

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

marche avec cp (GNU coreutils) 8.23

Edward Torvalds
la source