使用 Python 构建非常长的 rsync 包含和排除

使用 Python 构建非常长的 rsync 包含和排除

我正在尝试运行 Home 中所有 .x 文件夹的备份以保存配置。

使用我正在编写的程序,您可以选择要包含在备份中或从备份中排除的任何文件夹,此列表是动态的。

我正在尝试构建类似的东西:

rsync -aP --exclude $Home/.cache/google-chrome/Default/Cache/* --exclude $Home/.cache/google-chrome/Default/Media\ Cache/* --exclude $Home/.wine $HOME/.* /mnt/ext/

因此,在上面的例子中,除了 Wine 和 Chrome 的缓存之外,所有内容都复制到了外部驱动器。我知道我可以建立一个文本文件并告诉,rsync --exclude-from 'textfile'但我宁愿不必读取和重写文本文件。

我担心的是,随着它的扩展,排除列表可能会变得很长,因为我的项目最终将开始包括 $HOME 之外的文件夹以及其他系统的 NFS 共享。我可以启动命令并在变量中使用排除列表:

exlist = "--exclude $Home/.cache/google-chrome/Default/Cache/* --exclude $Home/.cache/google-chrome/Default/Media\ Cache/* --exclude $Home/.wine" etc etc
subprocess.Popen("rsync -options ",exlist," /source /dest")

但是,这是否会导致非常长的 shell 命令出现问题,以至于我必须将其分解为较小的块?我宁愿提前预防潜在的未来问题,并开始编写处理程序来完成此任务,而不是尝试稍后修复和修补它。

答案1

我发现我的大小限制不一定是命令的长度,而是参数的长度。 Shell 命令的长度可以是 100,000-200,000 个字符。 但是,参数的长度限制为几百个字符,具体取决于系统,这是通过使用 发现的getconf ARG_MAX

当我在参数中添加许多文件夹排除项时,这最终导致空间不足的问题rsync

为了解决这个问题,我需要以编程方式将rsync一次长时间执行的过程拆分为多个较小的过程。

执行此操作需要“边做边构建”,直到达到极限。程序将尝试构建所有单个排除项的字符串,每个排除项都位于一个列表(数组)中。如果超出参数长度限制,它将重新解析当前列表以进行更广泛的排除,从而减少总数,并存储这些排除的排除项。

以下是正在构建的排除列表的示例:

该列表称为excludes

(long list items...)  
--exclude $Home/.wine  
--exclude $Home/.cache/somefolders/*   
--exclude $Home/.cache/somefolders/*  
--exclude $Home/.cache/somefolders/*  
--exclude $Home/.cache/somefolders/*   
--exclude $Home/.cache/google-chrome/Default/Cache/*  
--exclude $Home/.cache/google-chrome/Default/Media\ Cache/* 
**LIMIT EXCEEDED**

现在,我们的 ARGS 限制已经超出。程序将重新解析此列表以查找常用文件夹,并创建第二个列表以保存以供下次执行rsync

解析后excludes重建为如下列表:

(long list items...)  
--exclude $Home/.wine  
--exclude $Home/.cache/*  

在解析时,被删除的行被放入另一个列表中,我们称之为它excludesnext看起来像这样:

--exclude $Home/.cache/somefolders/*   
--exclude $Home/.cache/somefolders/*  
--exclude $Home/.cache/somefolders/*  
--exclude $Home/.cache/somefolders/*   
--exclude $Home/.cache/google-chrome/Default/Cache/*  
--exclude $Home/.cache/google-chrome/Default/Media\ Cache/*

因此,所有重复文件夹都从 中移除excludes并放入 中excludesnext。随着项目被移动,缩短的宽文件夹被替换为,将使用这些新的较短文件夹创建excludes一个名为 的新列表。cutlist

因此,该cutlist列表包括:

--exclude $Home/.cache/* 

rsyncexcludes使用列表作为参数运行。

rsync完成后,excludes列表将被清空,并将excludesnext列表复制到excludes列表中。

然后使用新的缩短列表作为源rsync循环运行,并将所有匹配的子文件夹用作排除。cutlistexcludes

当然,cutlist列表中可能有许多项目,程序将循环遍历每个项目。第二次运行rsync循环运行。但在此循环中,当达到最大长度时,excludes列表的解析方式与之前相同,只是新的缩短文件夹被添加到cutlist而不是进入excludes。这样,当循环遍历 cutlist 时,它可以在遍历列表时自行扩展。当excludesnext使用列表项时,它们将被删除。

最终,这将导致所有文件夹和子文件夹被复制,包括所有排除项,无论有多少。完成最后一项意味着cutlist一切都已执行。

此方法可能会rsync运行几次甚至几十次,具体取决于您排除了多少个文件夹,但它永远不会重复。

抱歉,我没有发布实际的代码,但这是一个正在进行的混乱的工作,在我发布可运行的服务之前,我还不太愿意发布我的代码。

答案2

我知道我可以建立一个文本文件并告诉 rsync --exclude-from 'textfile' 但我不想读取和重写文本文件。

做就是了:

with contextlib.closing(tempfile.NamedTemporaryFile()) as exclude_from:
    print(*your_exclude_list, sep="\n", flush=True, file=exclude_from)  # etc
    subprocess.check_call(['rsync', '--exclude-from', exclude_from.name, ...])

...不用担心临时文件。我知道处理临时文件似乎很麻烦,但使用 Python 的库和上下文管理器,这些都可以很好地包装起来,所以你不必担心它。

相关内容