rsync:同步文件夹,但将其他文件保留在目标中


10

我开始rsync尝试使用它来使本地系统上的两个文件夹保持同步。我有一个源文件夹,其内容会随时间而变化(添加了一些文件,进行了一些更改并删除了一些文件)和一个目标文件夹,我几乎希望成为源文件夹的镜像。所以我尝试使用rsync像这样:

rsync -a --delete "${source_dir}" "${target_dir}";

这确实使目标的内容与源的内容完全相同。但是,我希望能够向目标而不是源添加一些文件,但是我不希望每次执行rsync时都将其删除。另一方面,曾经被同步然后在源中被删除的文件仍应删除。

有没有一种方法可以执行此操作而不必更改我要排除的每个文件的命令?

更新:我应该提到,我不仅限于rsync。如果另一个程序完成了任务,那也很好。我只是尝试使用rsync解决此问题。


@AszunesHeart,您好,很好奇,但您是否测试了答案?
Jacob Vlijm '17

您是否尝试过使用--delete选项?那就像Robocopy中的/ MIR选项。
SDsolar '17

Answers:


9

rsync有一个名为--exclude-fromoption的选项,它允许您创建一个文件,其中包含要排除的所有文件的列表。您可以在要添加新的排除项或删除旧的排除项时更新此文件。

如果在/home/user/rsync_exclude新命令中创建排除文件,将是:

rsync -a --delete --exclude-from="/home/user/rsync_exclude" "${source_dir}" "${target_dir}"

创建排除列表文件时,应将每个排除规则放在单独的行上。排除项是相对于您的源目录而言的。如果/home/user/rsync_exclude文件包含以下选项:

secret_file
first_dir/subdir/*
second_dir/common_name.*
  • secret_file源目录中调用的任何文件或目录都将被排除。
  • 中的所有文件都${source_dir}/first_dir/subdir将被排除,但其中的空版本subdir将被同步。
  • ${source_dir}/second_dir前缀为的所有文件都common_name.将被忽略。所以common_name.txtcommon_name.jpg等等。

我不确定这是否满足我的要求。另外,我发现列出要添加到目标的每个文件或文件夹都是不切实际的。我想有一个自动的方法来做到这一点。假设我在target中有各种脚本会生成多个日志文件(也在target中),并且我不想在rsync_exclude-file中列出这些文件的每个位置。有没有一种方法可以使rsync“记住”已同步的文件,并仅使它们受--delete影响?
jkrzefski'9

抱歉,我误解了您的问题,尽管您想添加到源中,但也没有将其更新为目标。我认为有一种方法可以执行您想要的操作,但是我必须仔细考虑一下。我有时间编辑后会发表评论。
Arronical '16

@jkrzefski如果要从目标中的另一个脚本生成文件,并想从源中排除它们,那么为什么不将那些日志文件的目标更改为另一个文件夹呢?据推测,如果您不同步它们,是因为它们不太重要。

6

自从您提到:我不仅限于rsync:

维护镜像的脚本,允许向目标添加额外的文件

脚本下面的内容完全符合您的描述。

该脚本可以在详细模式下运行(在脚本中进行设置),该模式将输出备份的进度(镜像)。无需说这也可以用来记录备份:

详细选项

在此处输入图片说明


这个概念

1.首次备份时,脚本:

  • 在目标目录中创建一个文件,列出所有文件和目录; .recentfiles
  • 创建目标目录中所有文件和目录的精确副本(镜像)

2.在下一个等等备份

  • 该脚本比较文件的目录结构和修改日期。源文件中的新文件和目录将复制到镜像中。同时,创建第二个(临时)文件,在源目录中列出当前文件和目录。.currentfiles
  • 随后,将.recentfiles(列出先前备份的情况)与进行比较.currentfiles。显然只有.recentfiles那些不在其中的文件才从.currentfiles源中删除,而将从目标中删除。
  • 脚本始终不会“手动”看到您手动添加到目标文件夹中的文件,而是将其保留下来。
  • 最后,将临时文件.currentfiles重命名为.recentfiles服务于下一个备份周期,依此类推。

剧本

#!/usr/bin/env python3
import os
import sys
import shutil

dr1 = sys.argv[1]; dr2 = sys.argv[2]

# --- choose verbose (or not)
verbose = True
# ---

recentfiles = os.path.join(dr2, ".recentfiles")
currentfiles = os.path.join(dr2, ".currentfiles")

if verbose:
    print("Counting items in source...")
    file_count = sum([len(files)+len(d) for r, d, files in os.walk(dr1)])
    print(file_count, "items in source")
    print("Reading directory & file structure...")
    done = 0; chunk = int(file_count/5); full = chunk*5

def show_percentage(done):
    if done % chunk == 0:
        print(str(int(done/full*100))+"%...", end = " ")

for root, dirs, files in os.walk(dr1):
    for dr in dirs:
        if verbose:
            if done == 0:
                print("Updating mirror...")
            done = done + 1
            show_percentage(done) 
        target = os.path.join(root, dr).replace(dr1, dr2)
        source = os.path.join(root, dr)
        open(currentfiles, "a+").write(target+"\n")
        if not os.path.exists(target):
            shutil.copytree(source, target)
    for f in files:
        if verbose:
            done = done + 1
            show_percentage(done)
        target = os.path.join(root, f).replace(dr1, dr2)
        source = os.path.join(root, f)
        open(currentfiles, "a+").write(target+"\n") 
        sourcedit = os.path.getmtime(source)
        try:
            if os.path.getmtime(source) > os.path.getmtime(target):
                shutil.copy(source, target)   
        except FileNotFoundError:
            shutil.copy(source, target)

if verbose:
    print("\nChecking for deleted files in source...")

if os.path.exists(recentfiles):
    recent = [f.strip() for f in open(recentfiles).readlines()]
    current = [f.strip() for f in open(currentfiles).readlines()]
    remove = set([f for f in recent if not f in current])
    for f in remove:
        try:
            os.remove(f)
        except IsADirectoryError:
            shutil.rmtree(f)
        except FileNotFoundError:     
            pass
        if verbose:
            print("Removed:", f.split("/")[-1])

if verbose:
    print("Done.")

shutil.move(currentfiles, recentfiles)

如何使用

  1. 将脚本复制到一个空文件中,另存为 backup_special.py
  2. 更改-如果需要-脚本开头的详细选项:

    # --- choose verbose (or not)
    verbose = True
    # ---
    
  3. 使用source和target作为参数运行它:

     python3 /path/to/backup_special.py <source_directory> <target_directory>
    

速度

我在10 GB目录中测试了该脚本,并在网络驱动器(NAS)上存储了约40.000个文件和目录,它几乎与rsync同时进行了备份。

在40.000个文件上,更新整个目录只花了rsync几秒钟的时间,这对于imo来说是可以接受的,也就不足为奇了,因为脚本需要将内容与上次进行的备份进行比较。


嗨@ Aszune'sHeart添加了脚本选项。如果一切都清楚,请提及。
雅各布·弗利姆
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.