rsync: синхронизировать папки, но сохранить дополнительные файлы в целевом устройстве

rsync: синхронизировать папки, но сохранить дополнительные файлы в целевом устройстве

Я только начал rsyncи попытался использовать его для синхронизации двух папок на локальной системе. У меня есть исходная папка, содержимое которой со временем меняется (некоторые файлы добавляются, некоторые изменяются, некоторые удаляются), и целевая папка, которую я хочу сделать почти зеркалом исходной. Поэтому я попытался использовать rsync следующим образом:

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

Это сохраняет содержимое target точно таким же, как и содержимое source. Однако я хотел бы иметь возможность добавлять некоторые файлы в target, а не в source, но я не хочу, чтобы они удалялись каждый раз, когда я делаю rsync. С другой стороны, файлы, которые раньше синхронизировались, а затем были удалены в source, все равно должны быть удалены.

Есть ли способ сделать это, не изменяя команду для каждого файла, который я хочу исключить?

Обновлять: Я должен упомянуть, что я не ограничен rsync. Если другая программа справится с этой задачей, это тоже хорошо. Я только что попытался решить эту проблему с помощью rsync.

решение1

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.txt, common_name.jpgи т. д.

решение2

Поскольку вы упомянули:Я не ограничиваюсь 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. Измените, если хотите, параметр verbose в заголовке скрипта:

    # --- choose verbose (or not)
    verbose = True
    # ---
    
  3. Запустите его с исходным кодом и целевой переменной в качестве аргументов:

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

Скорость

Я протестировал скрипт на каталоге размером 10 ГБ с примерно 40 000 файлов и каталогов на моем сетевом диске (NAS). Резервное копирование было выполнено примерно за то же время, что и rsync.

Обновлениевесь каталог скопировался всего на несколько секунд позже, чем rsync, на 40 000 файлов, что, на мой взгляд, приемлемо и неудивительно, поскольку скрипт должен сравнивать содержимое с последней сделанной резервной копией.

Связанный контент