5
votes

Comment copier uniquement le contenu du fichier modifié sur le fichier de destination déjà existant?

J'ai un script que j'utilise à des fins de copie d'un emplacement à un autre et le fichier sous la structure du répertoire sont tous des fichiers .txt .

Ce script évalue simplement le fichier size sur la source et ne copiez que si la taille du fichier n'est pas de zéro octet. Cependant, j'ai besoin d'exécuter ce script dans un cron après un certain intervalle pour copier les données incrémentées.

Donc, j'ai besoin de savoir comment copier uniquement le contenu du fichier qui sont mis à jour sur le fichier source, puis mettent à jour la destination uniquement pour le nouveau contenu et pas simplement écrasé s'il est déjà présent à la destination.

Code:

import os
import glob
import filecmp
import shutil
import datetime

def Copy_Logs():
    Info_month = datetime.datetime.now().strftime("%B")
    for filename in glob.glob("/data1/logs/{0}/*/*.txt".format(Info_month)):
        if os.path.getsize(filename) > 0:
            if not os.path.exists("/data2/logs/" + os.path.basename(filename)) or not filecmp.cmp("/data2/logs/" + os.path.basename(filename), "/data2/logs/"):
                shutil.copyfile(filename, "/data2/logs/")

if __name__ == '__main__':
    Copy_Logs()

Je cherche s'il existe un moyen d'utiliser shutil () de manière à ce que rsync fonctionne ou s'il existe un moyen alternatif au code Je l'ai.

En un mot, je dois copier uniquement les fichiers si ce n'est déjà fait, puis ne copier le delta que si la source est mise à jour.

Remarque: strong> Le Info_month = datetime.datetime.now (). strftime ("% B") est obligatoire à conserver car il détermine le répertoire actuel par nom de mois.

Modifier:

Juste avoir une autre idée brute si nous pouvons utiliser filecmp avec le module shutil.copyfile pour comparer des fichiers et diriger ories mais je ne sais pas comment intégrer cela dans le code.

#!/bin/python3
import os
import glob
import shutil
import datetime

def Copy_Logs():
    Info_month = datetime.datetime.now().strftime("%B")
    # The result of the below glob _is_ a full path
    for filename in glob.glob("/data1/logs/{0}/*/*.txt".format(Info_month)):
        if os.path.getsize(filename) > 0:
            if not os.path.exists("/data2/logs/" + os.path.basename(filename)):
                shutil.copy(filename, "/data2/logs/")

if __name__ == '__main__':
    Copy_Logs()


2 commentaires

Qu'en est-il de l'exécution de rsync via le sous-processus ?


@MikhailGerasimov, cela peut être une façon de procéder.


7 Réponses :


-2
votes

vous devrez intégrer une base de données, et vous pourrez conserver une trace des fichiers en fonction de la taille, du nom et de l'auteur.

en cas de mise à jour, il y aura un changement dans la taille du fichier, vous pouvez mettre à jour ou ajouter en conséquence


1 commentaires

Prakruti, je n'ai pas besoin d'intégrer la base de données et de conserver des enregistrements, il pourrait y avoir d'autres moyens pratiques comme rsync.



1
votes

Vous devez enregistrer les modifications quelque part ou écouter l'événement lorsque le contenu du fichier change. Pour ce dernier, vous pouvez utiliser watchdog .

Si vous préférez vraiment cron à la place de vérification incrémentielle des changements (chien de garde), vous devrez stocker les changements dans une base de données. Un exemple de base serait:

from subprocess import Popen, PIPE
diff = Popen(['diff', 'old.txt', 'new.txt']).communicate()[0]
Popen(['patch', 'old.txt'], stdin=PIPE).communicate(input=diff)

puis pour vérifier le diff vous videriez l'état avant cron dans un fichier, exécuteriez un simple diff old.txt new.txt et s'il y a une sortie (c'est-à-dire qu'il y a un changement), vous copieriez soit le fichier entier, soit simplement la sortie du diff seul que vous appliqueriez alors comme un patch au fichier que vous souhaitez écraser.

S'il n'y a pas de sortie diff , il n'y a aucun changement et donc rien à mettre à jour dans le fichier.

Modifier: En fait: D vous pourriez même ne pas avoir besoin d'une base de données si les fichiers sont sur la même machine ... De cette façon, vous pouvez simplement diff + patch directement entre l'ancien et le nouveau fichier.

Exemple:

$ echo 'hello' > old.txt && echo 'hello' > new.txt
$ diff old.txt new.txt                             # empty
$ echo 'how are you' >> new.txt                    # your file changed
$ diff old.txt new.txt > my.patch && cat my.patch  # diff is not empty now
1a2
> how are you

$ patch old.txt < my.patch  # apply the changes to the old file

et en Python avec la même base old.txt et new.txt :

ID | path        | state before cron
1  | /myfile.txt | hello
...| ...         | ...


2 commentaires

KeyWeeUsr, Merci pour la réponse avec une approche différente mais comment pourrais-je l'utiliser avec mon code actuel, je cherche une solution alternative sinon la mienne, je suis sur la même machine.


@ krock1516 J'ai ajouté un exemple. Il vous suffirait de le répéter pour chaque fichier de votre emplacement.



1
votes

Une façon consiste à enregistrer une seule ligne dans un fichier pour garder le suivi de la dernière heure (à l'aide de os.path.getctime ) vous avez copié les fichiers et maintenez cette ligne à chaque fois que vous copiez.

Remarque: L'extrait suivant peut être optimisé.

should_copy_files = [filename for filename in list_of_files if os.path.getctime(filename) > float(latest_file_modified_time)]

L'approche consiste à créer un fichier contenant l'horodatage du dernier fichier modifié par le système.

Récupérer tous les fichiers et les trier par heure de modification

latest_file_modified_time = os.path.getctime(list_of_files[0])

Au départ, dans sinon os.path.exists ("track_modifications. txt "): Je vérifie si ce fichier n'existe pas (c'est-à-dire la première fois à copier), puis j'enregistre le plus grand horodatage du fichier dans

list_of_files = sorted(glob.iglob('directory/*.txt'), key=os.path.getctime, reverse=True)

Et je il suffit de copier tous les fichiers donnés et d'écrire cet horodatage dans le fichier track_modifications .

sinon, le fichier existe (c'est-à-dire qu'il y avait des fichiers copiés avant), je vais juste lire cet horodatage et comparer avec la liste des fichiers que j'ai lus dans list_of_files et récupérez tous les fichiers avec un horodatage plus grand (c'est-à-dire créés après le dernier fichier que j'ai copié). C'est dans

import datetime
import glob
import os
import shutil

Info_month = datetime.datetime.now().strftime("%B")
list_of_files = sorted(glob.iglob("/data1/logs/{0}/*/*.txt".format(Info_month)), key=os.path.getctime, reverse=True)
if not os.path.exists("track_modifications.txt"):
    latest_file_modified_time = os.path.getctime(list_of_files[0])
    for filename in list_of_files:
            shutil.copy(filename, "/data2/logs/")
    with open('track_modifications.txt', 'w') as the_file:
        the_file.write(str(latest_file_modified_time))
else:
    with open('track_modifications.txt', 'r') as the_file:
        latest_file_modified_time = the_file.readline()
    should_copy_files = [filename for filename in list_of_files if
                         os.path.getctime(filename) > float(latest_file_modified_time)]
    for filename in should_copy_files:
            shutil.copy(filename, "/data2/logs/")

En fait, le suivi de l'horodatage des derniers fichiers modifiés vous donnerait également l'avantage de copier les fichiers déjà copiés lorsqu'ils sont modifiés :)


7 commentaires

Andrew, Merci pour la réponse détaillée, j'apprécie et je vais le vérifier, Cependant à la recherche de plus d'options avant de conclure à la vraie réponse :-)


Cependant, cela copie le fichier entier, ce qui sera un vrai problème au cas où les fichiers seraient volumineux au lieu de copier uniquement le diff. De plus, vous ne suivez aucune modification ici, donc un simple touch file.txt ferait en sorte que votre script copie les fichiers même si rien ne change en eux (disons qu'une application accède au fichier et le suit avec un horodatage ).


J'ai également besoin de suivre Info_month = datetime.datetime.now (). Strftime ("% B") comme je l'ai dans mon script, @Andrew, où est le chemin du répertoire de destination défini est ce "." .


Il est vrai qu'il copie le fichier entier s'il n'a pas été copié auparavant. Non, un simple touch file.txt ne fera pas copier tous les fichiers sauf le file.txt uniquement.


@ krock1516 Je changerai les chemins pour qu'ils soient comme les vôtres. J'étais juste en train d'illustrer une approche :)


@AndrewNaguib, d'accord.


Merci d'apprécier toutes vos contributions et réponse ... en attendant une autre approche à travers ce post.



2
votes

Vous pouvez utiliser le correctif de correspondance des différences de Google (vous pouvez l'installer avec pip install diff-match-patch ) pour créer un diff et appliquer un patch de celui-ci:

import diff_match_patch as dmp_module

#...
if not os.path.exists("/data2/logs/" + os.path.basename(filename)):
    shutil.copy(filename, "/data2/logs/")
else:
    with open(filename) as src, open("/data2/logs/" + os.path.basename(filename),
                                                                        'r+') as dst:
        dmp = dmp_module.diff_match_patch()

        src_text = src.read()
        dst_text = dst.read()

        diff = dmp.diff_main(dst_text, src_text)

        if len(diff) == 1 and diff[0][0] == 0:
            # No changes
            continue

        #make patch
        patch = dmp.patch_make(dst_text, diff)
        #apply it
        result = dmp.patch_apply(patch, dst_text)

        #write
        dst.seek(0)
        dst.write(result[0])
        dst.truncate()


9 commentaires

@ krock1516, l'installation de pip n'a pas fonctionné pour vous? Quelle erreur obtenez-vous?


Je n'ai jamais supprimé Info_month ... Je viens d'écrire le fragment intéressant du code pour que vous puissiez l'intégrer dans le vôtre.


BTW, vous pouvez télécharger la bibliothèque pip () et installez-le à partir du fichier local: packaging.python.org/tutorials/installing-packages/... .


Vous n'avez pas besoin d'accéder à l'extérieur. Si vous y avez accès ssh (avez-vous?), Vous pouvez copier le fichier téléchargé avec scp : hypexr.org/linux_scp_help.php


Je connais scp et d'autres mécanismes de transfert de fichiers, mais ma question est en premier lieu comment obtiendrez-vous le pkg avant de le copier alors que vous ne l'avez pas dans votre référentiel local / privé?


Comme je l'ai proposé précédemment, depuis une machine (personnelle?) Avec accès à Internet, et accès ssh à votre serveur, vous téléchargez le fichier ( pypi.org/project/diff-match-patch/#files ) puis copiez-le en utilisant scp .


ceci est mon système domestique, je ne peux pas copier sur mon système de bureau :(.


Comment accédez-vous à votre système de bureau? Comment y déployez-vous votre code?


C'est quelque chose d'extraordinaire, mais j'ai ma propre configuration de laboratoire à domicile pour ma R&D personnelle que j'ai utilisée pour tester et prendre la liberté de tester en écrivant la mienne. il n'y a aucun moyen d'accéder au code plutôt que de l'écrire à nouveau là-bas. ceci est juste une pièce de simulation de test.



1
votes

Il y a des idées très intéressantes dans ce fil, mais je vais essayer de proposer de nouvelles idées.

Idée no. 1: Meilleure façon de suivre les mises à jour

Selon votre question, il est clair que vous utilisez une tâche cron pour suivre le fichier mis à jour.

Si vous essayez de surveiller un petite quantité de fichiers / répertoires, je proposerais une approche différente qui vous simplifiera la vie.

Vous pouvez utiliser le mécanisme Linux inotify , qui vous permet de surveiller des fichiers / répertoires spécifiques et soyez averti chaque fois qu'un fichier est écrit.

Pro : Vous connaissez chaque écriture immédiatement, sans avoir besoin de vérifier les modifications. Vous pouvez bien sûr écrire un gestionnaire qui ne met pas à jour la destination pour chaque écriture, mais un dans X minutes.

Voici un exemple qui utilise le package python inotify (pris depuis la page du package ):

import inotify.adapters

def _main():
    i = inotify.adapters.Inotify()

    i.add_watch('/tmp')

    with open('/tmp/test_file', 'w'):
        pass

    for event in i.event_gen(yield_nones=False):
        (_, type_names, path, filename) = event

        print("PATH=[{}] FILENAME=[{}] EVENT_TYPES={}".format(
              path, filename, type_names))

if __name__ == '__main__':
    _main()

Idée no. 2: Copier uniquement les modifications

Si vous décidez d'utiliser le mécanisme inotify , il sera trivial de garder une trace de votre état.

Ensuite, là il existe deux possibilités:

1. Le nouveau contenu est TOUJOURS ajouté

Si tel est le cas, vous pouvez simplement copier n'importe quoi depuis votre dernier décalage jusqu'à la fin du fichier.

2 . Les nouveaux contenus sont écrits à des emplacements aléatoires.

Dans ce cas, je recommanderais également une méthode proposée par d'autres réponses: Utiliser des correctifs de diff. C'est de loin la solution la plus élégante à mon avis.

Voici quelques options:


2 commentaires

Daniel, merci pour votre réponse, je vais voir comment elle peut être utilisée, bien que quelques idées intéressantes :-)


@ krock1516, heureux de vous aider. S'il vous plaît laissez-moi savoir si vous avez besoin d'un approfondissement supplémentaire :)



1
votes

L'un des avantages de rsync est qu'il ne copie que les différences entre les fichiers. À mesure que les fichiers deviennent énormes, cela réduit considérablement les E / S.

Il existe une pléthore d'implémentations et de wrappers de type rsync autour du programme original dans PyPI. Ce article de blog décrit comment mettre en œuvre rsync en Python d'une très bonne manière, et peut être utilisé tel quel.

Quant à vérifier s'il est nécessaire de faire la synchronisation, vous pouvez utiliser filecmp.cmp () . Dans sa variante peu profonde, il ne vérifie que la signature os.stat () .


3 commentaires

@ - igrinis, merci pour votre réponse Je l'apprécie, oui je sais que rsync est un moyen polyvalent pour ce type de requête mais que ce que je recherche mon code Cependant, avec l'implémentation python, je peux simplement l'utiliser comme rsync - av --min-size = 1 sourc_path Dest_path et tout est fait.


Je pense que vous avez manqué la partie post de blog dans la réponse;)


NP. Si c'est quelque chose comme des fichiers journaux, vous pouvez supposer que toutes les différences se trouvent à la fin du fichier et ne copier que la partie de fin. Ensuite, il vous suffit de vérifier la taille, et si elle diffère, de copier à partir du fichier d'origine à partir de la taille de fichier DESTINATION et de l'ajouter à la copie.



2
votes

Comme mentionné ci-dessus, rsync est un meilleur moyen de faire ce genre de travail où vous devez effectuer une liste de fichiers incrémentielle ou dire delta des données Donc, je préférerais le faire avec rsync et < code> sous-processus tout au long du module.

Cependant, vous pouvez également attribuer une variable Curr_date_month pour obtenir la date, le mois et l'année actuels en tant que votre condition pour simplement copier les fichiers de l'actuel Dossier mois et jour. aussi vous pouvez définir la variable source et destination juste pour la facilité de les écrire dans le code.

Deuxièmement, bien que vous ayez une vérification de la taille du fichier avec getsize mais Je voudrais ajouter un paramètre d'option rsync --min-size = pour m'assurer de ne pas copier le fichier de zéro octet.

Votre code final se trouve ici.

< pré> XXX


2 commentaires

@ - pygo, merci d'apprécier votre aide, c'est un peu que je cherche, je vais le tester et le vérifier.


Cela fonctionne comme je l'ai testé, mais à la recherche d'autres commentaires et réponses pour conclure la réponse.