如何仅将已更改的文件内容复制到已存在的目标文件上?

时间:2019-01-15 10:38:58

标签: python python-3.x subprocess rsync shutil

我有一个脚本,我正在使用该脚本从一个位置复制到另一位置,并且目录结构下的文件都是.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"),因为它会根据月份名称确定当前目录。

编辑:

如果我们可以将filecmpshutil.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 :(得分:2)

您可以使用Google's 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()

答案 1 :(得分:1)

您需要将更改保存在某个地方,或者在文件内容更改时收听事件。对于后者,您可以使用watchdog

如果您决定真正喜欢cron而不是增量检查更改(看门狗),则需要将更改存储在某个数据库中。一些基本的例子是:

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

然后检查diff,然后将cron之前的状态转储到文件中,运行简单的diff old.txt new.txt,如果有输出(例如,有更改),则可以复制整个文件或仅是diff的输出,然后将它们作为patch应用于要覆盖的文件。

如果没有diff输出,则没有更改,因此文件中没有要更新的内容。

编辑:实际上,如果文件位于同一台计算机上,则:D您甚至可能不需要数据库...这样,您就可以直接在新旧文件之间进行diff + patch。 / p>

示例:

$ 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.txtnew.txt基:

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

答案 2 :(得分:1)

一种方法是在文件中保存一行以跟踪您复制文件的最新时间(并在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)]

实际上,跟踪最新修改文件的时间戳也将为您提供一个优点,即当文件被更改时复制已复制的文件:)

答案 3 :(得分:1)

此主题中有一些非常有趣的想法,但是我将尝试提出一些新想法。

想法否。 1:跟踪更新的更好方法

根据您的问题,很明显,您正在使用cron作业来跟踪更新的文件。

如果您要监视相对少量的文件/目录,我建议使用另一种方法来简化您的生活。

您可以使用Linux inotify 机制,该机制允许您监视特定的文件/目录,并在每次写入文件时得到通知。

专业版:您立即知道每笔写入,而无需检查更改。您当然可以编写一个处理程序,该处理程序不会在每次写入时更新目标,而是在X分钟内更新一次。

以下是使用inotify python软件包(取自package's page)的示例:

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:仅复制更改

如果您决定使用 intify 机制,则跟踪状态非常简单。

然后,有两种可能性:

1。总是添加新内容

在这种情况下,您只需复制上一个偏移量到文件末尾的任何内容即可。

2。新内容写在随机位置

在这种情况下,我还将建议其他答案提出的一种方法:使用差异补丁。在我看来,这是迄今为止最优雅的解决方案。

一些选项如下:

答案 4 :(得分:1)

rsync的好处之一是它仅复制文件之间的差异。随着文件变得巨大,它会大大减少I / O。

PyPI中的原始程序周围有大量类似rsync的实现和包装。 blog post描述了如何以很好的方式在Python中实现rsync,并且可以按原样使用。

要检查是否完全需要进行同步,可以使用filecmp.cmp()。在较浅的变体中,它仅检查os.stat()签名。

答案 5 :(得分:1)

如上所述,rsync是执行此类作业的更好方法,需要执行增量文件列表或说出数据增量,因此,我宁愿使用rsync和{{1} }模块。

但是,您也可以分配变量subprocess来获取当前日期,月份和年份,这只是从“当前月份和日期”文件夹中复制文件的要求。同样,您也可以定义源变量和目标变量,以便于将它们写到代码中。

第二,虽然您使用Curr_date_month检查了文件大小,但是我想添加一个rsync选项参数getsize以确保不复制零字节文件。

您的最终代码在这里。

--min-size=

答案 6 :(得分:-2)

您必须集成一个数据库,并且可以根据文件的大小,名称和作者来记录文件。

如果有任何更新,文件大小将发生变化,您可以相应地更新或附加