Comment puis-je effectuer une opération de “copie en cas de modification”?

34

Je voudrais copier un ensemble de fichiers du répertoire A dans le répertoire B, avec l’avertissement que si un fichier du répertoire A est identique à un fichier du répertoire B, il ne doit pas être copié (et donc son heure de modification ne doit pas être modifiée). mis à jour). Y at-il un moyen de faire cela avec les outils existants, sans écrire mon propre script pour le faire?

Pour en dire un peu plus sur mon cas d'utilisation: je suis en train de générer automatiquement un groupe de .cfichiers dans un répertoire temporaire (par une méthode qui doit tous les générer de manière inconditionnelle), et lorsque je les régénère , j'aimerais copier uniquement ceux qui ont changé dans le répertoire source réel, laissant les inchangés inchangés (avec leurs anciens temps de création) afin que makesachent qu’il n’est pas nécessaire de les recompiler. (Tous les fichiers générés ne sont pas des .cfichiers, cependant, je dois donc faire des comparaisons binaires plutôt que des comparaisons de texte.)

(Remarque: ceci découle de la question que j'ai posée à l' adresse https://stackoverflow.com/questions/8981552/speeding-up-file-comparions-with-cmp-on-cygwin/8981762#8981762 , où j'essayais. pour accélérer le fichier de script que j'utilisais pour effectuer cette opération, mais il me semble que je devrais vraiment demander s'il existe un meilleur moyen de le faire que d'écrire mon propre script - en particulier depuis tout moyen simple de le faire dans un shell Le script invoquera quelque chose comme cmpsur chaque paire de fichiers, et le démarrage de tous ces processus prend trop de temps.)

Brooks Moses
la source
1
Vous pouvez utiliser diff -qr dirA dirBpour voir quels fichiers sont uniques dirAet dirB, respectivement.
1
@ brooks-moses c'est vraiment un travail qui convient à ccache !
Aculich
3
@hesse si vous voulez montrer les fichiers uniques, vous pouvez utiliser diff, mais si vous voulez voir ce qui a changé, utilisez rsync -avncou le long chemin à parcourir rsync --archive --verbose --dry-run --checksum.
Aculich

Réponses:

29

rsync est probablement le meilleur outil pour cela. Il y a beaucoup d'options sur cette commande, lisez donc la page de manuel . Je pense que vous voulez l'option --checksum ou le --ignore-times

Adam Terrey
la source
J'aurais dû noter que j'ai déjà essayé cela, sans succès. Ces deux options n'affectent que le fait que rsync effectue une copie - mais même s'il ne le fait pas, il met à jour l'heure de modification du fichier cible pour qu'elle soit identique à la source (si l' -toption est spécifiée) ou à l'heure de synchronisation. (si -tn'est pas spécifié).
Brooks Moses
4
@ Brooks Moses: Ce n'est pas. Au moins ma version de rsyncne le fait pas. Si je fais ceci :, mkdir src dest; echo a>src/a; rsync -c src/* dest; sleep 5; touch src/a; rsync -c src/* destalors stat dest/amontre que mtime et ctime ont 5 secondes de plus que ceux de src/a.
angus
@angus: Hein. D'accord, vous avez raison. La clé semble être l' --checksumoption, et bien que linux.die.net/man/1/rsync ne contienne absolument rien qui impliquerait que cela ait une incidence sur le fait que la date de modification soit mise à jour, il en résulte néanmoins que la date de modification de destination est laissée. intacte. (Par contre, l' --ignore-timesoption n'a pas cet effet; avec elle la date de modification est toujours mise à jour.) Étant donné que cela semble être entièrement non documenté, puis-je m'appuyer dessus?
Brooks Moses
2
@BrooksMoses: Je pense que vous pouvez compter sur lui: rsyncle flux de travail est le suivant: 1) vérifiez si le fichier doit être mis à jour; 2) si oui, mettez à jour le fichier. L' --checksumoption indique qu'il ne devrait pas être mis à jour et rsyncne devrait donc pas passer à l'étape 2).
enzotib
2
@BrooksMoses: --ignore-timessans --checksumserait copier tous les fichiers, et donc mettre à jour l'horodatage, même si les fichiers sont identiques.
enzotib
13

Vous pouvez utiliser le -ucommutateur pour cpaimer ainsi:

$ cp -u [source] [destination]

De la page de manuel:

   -u, --update
       copy only when the SOURCE file is newer than the destination file or 
       when the destination file is missing
gu1
la source
4
Bonjour et bienvenue sur le site. Nous nous attendons à ce que les réponses soient un peu plus substantielles ici. Par exemple, vous auriez pu inclure une explication de ce que le -udrapeau fait et comment il fonctionne et comment cela aiderait le PO. Toutefois, dans ce cas particulier, cela n’aiderait pas le PO, car il copierait des fichiers identiques s’ils étaient plus récents et modifierait ainsi leur horodatage, ce que le PO veut précisément éviter.
terdon
1
D'après un commentaire sur un A similaire déjà supprimé: "Cela ne fonctionnera pas, car cela copierait également des fichiers identiques, si l'horodatage de la source est plus récent (et met donc à jour l'horodatage de la destination avec la requête OP)."
slm
Ne répond pas du tout à la question, mais je l'ai quand même trouvée utile.
user31389
7

Bien que l’utilisation rsync --checksumsoit un bon moyen général de «copier s’il est modifié», il existe dans votre cas une solution encore meilleure!

Si vous voulez éviter de recompiler inutilement des fichiers, utilisez ccache qui a été construit exactement à cette fin! En fait, non seulement cela évitera les recompilations inutiles de vos fichiers générés automatiquement, mais cela accélérera les choses chaque fois que vous le ferez make cleanet vous recompilerez à partir de zéro.

Ensuite, je suis sûr que vous demanderez: "Est-ce que c'est sans danger?" Eh bien, oui, comme l'indique le site Web:

Est-ce sûr?

Oui. L'aspect le plus important d'un cache de compilateur est de toujours produire exactement le même résultat que le vrai compilateur. Cela inclut la fourniture exacte des mêmes fichiers objet et des mêmes avertissements du compilateur qui seraient générés si vous utilisiez le compilateur réel. La seule façon de savoir que vous utilisez ccache est la vitesse.

Et il est facile à utiliser en l'ajoutant simplement comme préfixe dans la CC=ligne de votre fichier makefile (ou vous pouvez utiliser des liens symboliques, mais la méthode makefile est probablement meilleure).

aculich
la source
1
Au début, j'avais mal compris et je pensais que vous suggérez d’utiliser ccache pour une partie de la génération, mais je comprends maintenant - vous avez suggéré que je copie simplement tous les fichiers, puis que j utilise ccache dans le processus de construction, évitant ainsi de reconstruire ceux qui n'avait pas changé. C'est une bonne idée, mais dans mon cas, cela ne va pas - j'ai des centaines de fichiers, je ne les modifie généralement qu'un ou deux à la fois, et je cours sous Cygwin, où il suffit de démarrer les centaines de processus ccache pour les examiner. fichier prendrait plusieurs minutes. Néanmoins, votez parce que c'est une bonne réponse pour la plupart des gens!
Brooks Moses
Non, je ne proposais pas de copier tous les fichiers, vous pouvez simplement générer automatiquement vos fichiers .c sur place (supprimez l’étape de la copie et écrivez-leur directement). Et puis utilisez simplement ccache. Je ne sais pas ce que vous entendez par démarrage de centaines de processus ccache. Il s’agit simplement d’un wrapper léger autour de gcc qui est assez rapide et qui accélérera également la reconstruction d’autres parties de votre projet. Avez-vous essayé de l'utiliser? Je voudrais voir une comparaison du timing entre l’utilisation de votre méthode de copie et celle de ccache. En fait, vous pouvez combiner les deux méthodes pour obtenir les avantages des deux.
Aculich
1
D'accord, d'accord, je comprends maintenant la copie. Pour clarifier, ce que je veux dire est ceci: si je génère les fichiers en place, je dois ensuite appeler ccache file.c -o file.oou l’équivalent, plusieurs centaines de fois car il existe plusieurs centaines de file.cfichiers. Quand je faisais cela avec cmp, plutôt que ccache, cela a pris plusieurs minutes - et cmpest aussi léger que ccache. Le problème est que, sur Cygwin, le démarrage d’un processus prend un temps non négligeable, même pour un processus complètement trivial.
Brooks Moses
1
En tant que point de données, for f in src/*; do /bin/true.exe; doneprend 30 secondes, alors oui. Quoi qu'il en soit, je préfère mon éditeur basé sur Windows et, mis à part ce type de problème de synchronisation, Cygwin fonctionne assez bien avec mon flux de travail en tant qu'emplacement léger pour tester des choses localement si je ne télécharge pas sur les serveurs de build. Il est utile d'avoir mon shell et mon éditeur dans le même système d'exploitation. :)
Brooks Moses
1
Si vous souhaitez utiliser votre éditeur Windows, vous pouvez le faire assez facilement avec les dossiers partagés si vous installez Guest Additions ... mais bon, si Cygwin vous convient, alors qui dois-je en dire autrement? Il me semble dommage de devoir franchir des étapes aussi étranges que celle-ci ... et la compilation en général serait également plus rapide dans une machine virtuelle.
Aculich
3

Cela devrait faire ce dont vous avez besoin

diff -qr ./x ./y | awk '{print $2}' | xargs -n1 -J% cp % ./y/

Où:

  • x est votre dossier mis à jour / nouveau
  • y est la destination que vous voulez copier
  • awk prendra le deuxième argument de chaque ligne de la commande diff (peut-être aurez-vous besoin de quelques informations supplémentaires pour les noms de fichiers avec un espace - vous ne pouvez pas l'essayer maintenant)
  • xargs -J% insérera le nom du fichier dans cp au bon endroit
Patkos Csaba
la source
1
-1 parce que c'est trop compliqué, non portable ( -Jest spécifique à bsd; avec GNU xargs c'est le cas -I), et ne fonctionne pas correctement si le même ensemble de fichiers n'existe pas déjà aux deux emplacements (si touch x/booGrep me le donne alors) Only in ./x: booqui provoque des erreurs dans le pipeline). Utilisez un outil conçu pour le travail, comme rsync --checksum.
Aculich
Ou mieux encore, pour ce cas spécifique, utilisez ccache .
Aculich
+1 parce que son un ensemble de commandes bien connues que je peux briser utiliser des tâches similaires (venu ici pour faire une diff), peut encore rsync mieux pour cette tâche particulière
NTG
3

J'aime utiliser l' unisson en faveur de rsynccar il supporte plusieurs maîtres, ayant déjà configuré mes clés SSH et mon vpn séparément.

Donc, dans ma crontab d’un seul hôte, je les laisse se synchroniser toutes les 15 minutes:

*** dev -logfile /tmp/sync.master.dev.log) &> /tmp/sync.master.dev.log

Ensuite, je pourrai me développer de part et d’autre et les changements se propageront. En fait, pour les projets importants, jusqu'à 4 serveurs reflètent le même arbre (3 exécutent à l'unisson à partir de cron, en indiquant celui qui ne le fait pas). En fait, les hôtes Linux et Cygwin sont mixtes - sauf qu’il ne faut pas s’attendre à des liens souples dans win32 en dehors de l’environnement cygwin.

Si vous allez dans cette voie, faites le miroir initial du côté vide sans le -batch, c'est-à-dire

unison -ui text  -times /home/master ssh://192.168.1.12//home/master -path dev

Bien sûr, il existe une configuration pour ignorer les fichiers de sauvegarde, les archives, etc.:

 ~/.unison/default.prf :
# Unison preferences file
ignore = Name {,.}*{.sh~}
ignore = Name {,.}*{.rb~}
ignore = Name {,.}*{.bak}
ignore = Name {,.}*{.tmp}
ignore = Name {,.}*{.txt~}
ignore = Name {,.}*{.pl~}
ignore = Name {.unison.}*
ignore = Name {,.}*{.zip}

    # Use this command for displaying diffs
    diff = diff -y -W 79 --suppress-common-lines

    ignore = Name *~
    ignore = Name .*~
    ignore = Path */pilot/backup/Archive_*
    ignore = Name *.o
Marcos
la source
J'ai regardé cela, mais je ne pouvais pas trouver une unisonoption qui signifie "ne met pas à jour les dates de fichier modifiées en dernier". Est-ce qu'il y a un? Sinon, c’est une excellente réponse à un problème totalement différent.
Brooks Moses
1
-timesfait ça pour moi. Unison a aussi un mode de fonctionnement à sec, pense-t-il.
Marcos
Bien, mettre times=false(ou laisser -times) ferait ça. Je ne sais pas comment j'ai raté cela dans la documentation auparavant. Merci!
Brooks Moses
Heureux de vous aider. Je suis un fainéant lorsqu'il s'agit de préserver des éléments comme les modifications de temps, les autorisations et les liens symboliques. Souvent négligé
Marcos
1

Bien que rsync --checksumla réponse soit correcte, notez que cette option est incompatible avec --times, et cela --archiveinclut --times, donc si vous voulez rsync -a --checksum, vous en avez vraiment besoin rsync -a --no-times --checksum.

Vladimir Kornea
la source
Que voulez-vous dire par «incompatible»?
ov
Que voulez-vous dire par "est la bonne réponse"?
thoni56