Как скопировать только измененное содержимое файла в уже существующий конечный файл? - PullRequest
0 голосов
/ 15 января 2019

У меня есть скрипт, который я использую для копирования из одного места в другое, и все файлы под структурой каталогов - это .txt файлы.

Этот скрипт просто оценивает размер файла в источнике и копирует только, если размер файла не равен нулю. Однако мне нужно запустить этот скрипт через cron через определенные промежутки времени, чтобы скопировать любые увеличенные данные.

Итак, мне нужно знать, как копировать только содержимое файла, который обновляется в исходном файле, а затем обновлять место назначения только для нового содержимого, а не просто перезаписывать, если оно уже присутствует в месте назначения.

Код:

#!/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()

Я смотрю, есть ли способ использовать shutil() таким образом, как работает rsync, или есть ли альтернативный способ кода, который у меня есть.

Короче говоря, мне нужно копировать только файлы, если они еще не скопированы, а затем копировать только дельту, если источник обновляется.

Примечание: Info_month = datetime.datetime.now().strftime("%B") является обязательным для сохранения, поскольку это определяет текущий каталог по названию месяца.

Edit:

Просто еще одна необработанная идея, если мы можем использовать filecmp с модулем shutil.copyfile для сравнения файлов и каталогов, но я не понимаю, как вписать это в код.

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()

Ответы [ 7 ]

0 голосов
/ 22 января 2019

Одним из преимуществ rsync является то, что он копирует только различия между файлами. Когда файлы становятся большими, это резко сокращает число операций ввода-вывода.

Существует множество rsync -подобных реализаций и оболочек вокруг исходной программы в PyPI. Это сообщение в блоге описывает, как очень хорошо реализовать rsync в Python, и может использоваться как есть.

Что касается проверки того, нужно ли вообще выполнять синхронизацию, вы можете использовать filecmp.cmp(). В его поверхностном варианте проверяется только подпись os.stat().

0 голосов
/ 22 января 2019

Как упомянуто выше, rsync - лучший способ выполнить такую ​​работу, когда вам нужно выполнить добавочный список файлов или сказать дельту данных. Поэтому я бы предпочел делать это с помощью rsync и модуля subprocess все время. .

Однако вы также можете назначить переменную Curr_date_month, чтобы получить текущую дату, месяц и год в качестве требования просто скопировать файлы из папки текущего месяца и дня. также вы можете определить исходную и целевую переменные просто для простоты записи их в код.

Во-вторых, хотя у вас есть проверка размера файла с getsize, но я бы хотел добавить параметр опции rsync --min-size=, чтобы не копировать файл нулевого байта.

Ваш окончательный код находится здесь.

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

def Copy_Logs():
    # Variable Declaration to get the month and Curr_date_month
    Info_month = datetime.datetime.now().strftime("%B")
    Curr_date_month = datetime.datetime.now().strftime("%b_%d_%y") 
    Sourcedir = "/data1/logs"
    Destdir = "/data2/logs/"
    ###### End of your variable section #######################
    # The result of the below glob _is_ a full path
    for filename in glob.glob("{2}/{0}/{1}/*.txt".format(Info_month, Curr_date_month, Sourcedir)):
        if os.path.getsize(filename) > 0:
            if not os.path.exists(Destdir + os.path.basename(filename)):
                subprocess.call(['rsync', '-avz', '--min-size=1', filename, Destdir ])

if __name__ == '__main__':
    Copy_Logs()
0 голосов
/ 20 января 2019

В этой теме есть несколько очень интересных идей, но я постараюсь предложить несколько новых идей.

Идея №. 1: лучший способ для отслеживания обновлений

По вашему вопросу ясно, что вы используете задание cron для отслеживания обновленного файла.

Если вы пытаетесь отслеживать относительно небольшое количество файлов / каталогов, я бы предложил другой подход, который упростит вашу жизнь.

Вы можете использовать механизм Linux inotify , который позволяет отслеживать определенные файлы / каталоги и получать уведомления при записи в файл.

Pro : Вы знаете каждую запись сразу, без необходимости проверять изменения. Конечно, вы можете написать обработчик, который не обновляет место назначения для каждой записи, но один за X минут.

Вот пример, в котором используется пакет inotify python (взят из страницы пакета ):

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()

Идея №. 2: Копирование только изменений

Если вы решите использовать механизм inotify , отследить ваше состояние будет тривиально.

Тогда есть две возможности:

1. Новое содержание ВСЕГДА добавлено

Если это так, вы можете просто скопировать что-нибудь из вашего последнего смещения до конца файла.

2. Новое содержимое пишется в случайных местах

В этом случае я бы порекомендовал также метод, предложенный другими ответами: использование diff-патчей. На мой взгляд, это самое элегантное решение.

Вот некоторые варианты:

0 голосов
/ 20 января 2019

Вы можете использовать Google Diff Match Patch (вы можете установить его с помощью pip install diff-match-patch), чтобы создать diff и применить к нему патч:

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()
0 голосов
/ 20 января 2019

Один из способов - сохранить одну строку в файле, чтобы отслеживать последнее время (с помощью os.path.getctime), когда вы копировали файлы, и сохраняйте эту строку при каждом копировании.

Примечание: следующий фрагмент может быть оптимизирован.

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/")

Подход заключается в создании файла, который содержит метку времени последнего файла, который был изменен системой.

Извлечение всех файлов и их сортировка по времени модификации

list_of_files = sorted(glob.iglob('directory/*.txt'), 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])

И я просто копирую все предоставленные файлы и записываю эту метку времени в файл track_modifications.

в противном случае файл существует (т. Е. Ранее были скопированы файлы), я просто захожу читать эту временную метку и сравниваю ее со списком файлов, которые я прочитал в list_of_files, и извлекаю все файлы с большей временной меткой (т. Е. Созданные после того, как последний файл я скопировал). Это в

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

На самом деле, отслеживание метки времени последних измененных файлов также даст вам преимущество копирования файлов, которые уже были скопированы при их изменении :)

0 голосов
/ 20 января 2019

Вам нужно сохранить изменения где-нибудь или прослушать событие, когда меняется содержимое файла. Для последнего вы можете использовать watchdog.

Если вы решите, что вы действительно предпочитаете cron, вместо того, чтобы постепенно проверять изменения (сторожевой таймер), вам нужно будет сохранить изменения в некоторой базе данных. Некоторым базовым примером будет:

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

затем, чтобы проверить diff, вы передали бы состояние перед cron в файл, запустили простой diff old.txt new.txt и, если есть какой-то вывод (то есть, есть изменение), вы бы скопируйте либо весь файл, либо только вывод одного только разностного файла, который вы затем примените как patch к файлу, который вы хотите перезаписать.

В случае отсутствия вывода diff, нет изменений и, следовательно, нет ничего для обновления в файле.

Редактировать: На самом деле: D вам может даже не понадобиться база данных, если файлы находятся на одном компьютере ... Таким образом, вы можете просто diff + patch непосредственно между старыми и новыми файлами.

Пример:

$ 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

и в Python с одинаковыми old.txt и new.txt базами:

from subprocess import Popen, PIPE
diff = Popen(['diff', 'old.txt', 'new.txt']).communicate()[0]
Popen(['patch', 'old.txt'], stdin=PIPE).communicate(input=diff)
0 голосов
/ 15 января 2019

вам нужно будет интегрировать базу данных, и вы можете вести учет файлов в соответствии с размером, именем и автором.

В случае каких-либо обновлений будет изменен размер файла, вы можете обновить или добавить соответственно

...