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

Posted

技术标签:

【中文标题】如何仅复制已存在的目标文件上更改的文件内容?【英文标题】:How to copy only the changed file-contents on the already existed destination file? 【发布时间】:2019-06-09 08:48:17 【问题描述】:

我有一个脚本,用于从一个位置复制到另一个位置,目录结构下的文件都是.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()

我正在寻找是否有办法以rsync 工作的方式使用shutil(),或者是否有替代我拥有的代码的方法。

简而言之,如果尚未复制文件,我只需复制文件,然后仅在源更新时复制增量。

注意: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()

【问题讨论】:

运行rsync 到subprocess 怎么样? @MikhailGerasimov,这可能是一种方法。 【参考方案1】:

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

【讨论】:

@krock1516,pip install 对你不起作用?你得到哪个错误? 我从未删除过Info_month...我只是写了一段有趣的代码,供您将其集成到您的代码中。 顺便说一句,您可以下载pip库(files.pythonhosted.org/packages/f0/2a/…)并从本地文件安装它:packaging.python.org/tutorials/installing-packages/…。 您不需要访问外部。如果您有 ssh 访问权限(是吗?),您可以使用 scp 复制下载的文件:hypexr.org/linux_scp_help.php 我知道 scp 和其他文件传输机制,但首先我的问题是,当您没有将 pkg 复制到本地/私有存储库中时,如何在将它复制之前获取它?【参考方案2】:

如前所述,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()

【讨论】:

@- pygo,感谢您的帮助,这是我正在寻找的东西,我会测试并检查它。 它的工作原理我测试过,但是寻找其他一些 cmets 和答案来总结答案。【参考方案3】:

一种方法是将单行保存到文件中,以跟踪您复制文件的最新时间(在os.path.getctime 的帮助下)并在每次复制时维护该行。

注意:下面的sn-p可以优化。

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

实际上,跟踪最新修改文件的时间戳还可以为您提供复制已被复制的文件的优势当它们被更改时 :)

【讨论】:

安德鲁,感谢您提供详细的答案,我很感激,我会检查一下,但是在得出真正的答案之前寻找更多选择:-) 然而,这会复制整个文件,如果文件很大而不是仅复制差异,这将是一个真正的问题。此外,您不会在此处跟踪任何更改,因此一个简单的touch file.txt 将使您的脚本复制文件,即使它们没有任何更改(假设应用程序访问该文件并使用时间戳跟踪它)。 我还需要跟踪Info_month = datetime.datetime.now().strftime("%B"),就像我在脚本中所做的那样,@Andrew,其中定义的目标目录路径是"." 如果之前没有复制过,它会复制整个文件。不,一个简单的touch file.txt 不会让它复制所有文件,但只复制file.txt 感谢您的所有意见和回答。只是等待这篇文章中的其他方法。【参考方案4】:

这个帖子有一些非常有趣的想法,但我会尝试提出一些新的想法。

想法没有。 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:仅复制更改

如果您决定使用 inotify 机制,那么跟踪您的状态将是微不足道的。

那么,有两种可能:

1.总是附加新内容

如果是这种情况,您可以简单地复制从最后一个偏移量到文件末尾的任何内容。

2。新内容写入随机位置

在这种情况下,我也会推荐其他答案提出的方法:使用差异补丁。这是迄今为止我认为最优雅的解决方案。

这里有一些选项:

diff-match-patch diff-and-patch

【讨论】:

Daniel,非常感谢您的回答,我会看看如何使用它,虽然有一些有趣的想法 :-) @krock1516,很乐意为您提供帮助。如果您需要进一步深入研究,请告诉我:)【参考方案5】:

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

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

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

【讨论】:

@- igrinis,感谢您的回答,我很感激,是的,我知道 rsync 是此类请求的通用方式,但我正在寻找我的代码但是,使用 python 实现我可以简单将其用作rsync -av --min-size=1 sourc_path Dest_path 并完成。 我认为您错过了答案中的博客文章部分;) NP。如果它类似于日志文件,您可以假设所有差异都在文件的末尾,并且只复制尾部。然后您只需检查大小,如果不同,则从 DESTINATION 文件大小开始从原始文件复制,然后附加到副本。【参考方案6】:

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

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

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

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

如果没有diff 输出,则文件中没有任何更改,因此无需更新。

编辑:实际上 :D 如果文件在同一台机器上,你甚至可能不需要数据库......这样你就可以直接在新旧文件之间进行差异和补丁。

示例:

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

【讨论】:

KeyWeeUsr,感谢您使用不同方法的答案,但我如何使用我当前的代码,我正在寻找替代解决方案,如果不是我的,我在同一台机器上。 @krock1516 我添加了一个示例。您只需要为您所在位置的每个文件重复此操作即可。【参考方案7】:

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

如果有任何更新,文件大小会有所变化,您可以相应地更新或追加

【讨论】:

Prakruti,我不需要集成数据库和保存记录,可能还有其他方便的方法,如 rsync。

以上是关于如何仅复制已存在的目标文件上更改的文件内容?的主要内容,如果未能解决你的问题,请参考以下文章

linux学习

第四天

cp命令

如何使用`docker cp``--no-clobber`?从Docker容器复制文件,但如果目标已经存在,则不覆盖]]

如何确定文件是不是已复制到目标

Day5-1 cp mv和cat等查看命令