rsync: Synchronizuj foldery, ale utrzymuj dodatkowe pliki w miejscu docelowym

10

Zaczynam i staram się rsyncgo używać do synchronizacji dwóch folderów w systemie lokalnym. Mam folder źródłowy, którego zawartość zmienia się w czasie (niektóre pliki są dodawane, niektóre zmiany, a niektóre usuwane) oraz folder docelowy, który chcę prawie być lustrem źródła. Więc próbowałem użyć rsync w następujący sposób:

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

Dzięki temu zawartość celu jest dokładnie taka sama jak zawartość źródła. Chciałbym jednak móc dodać niektóre pliki do celu, a nie źródła, ale nie chcę, aby były usuwane za każdym razem, gdy rsync. Z drugiej strony pliki, które były zsynchronizowane, a następnie usunięte w źródle, powinny być nadal usuwane.

Czy można to zrobić bez zmiany polecenia dla każdego pliku, który chcę wykluczyć?

Aktualizacja : Powinienem wspomnieć, że nie jestem ograniczony do rsync. Jeśli inny program wykona zadanie, to też dobrze. Właśnie próbowałem rozwiązać ten problem za pomocą rsync.

jkrzefski
źródło
Cześć @AszunesHeart, po prostu ciekawy, ale czy przetestowałeś / -aś odpowiedzi?
Jacob Vlijm
Czy próbowałeś skorzystać z opcji --delete? Ten jest jak opcja / MIR w Robocopy.
SDsolar

Odpowiedzi:

9

rsyncma opcję o nazwie --exclude-fromopcja, która pozwala utworzyć plik zawierający listę plików, które chcesz wykluczyć. Możesz zaktualizować ten plik za każdym razem, gdy chcesz dodać nowe wykluczenie lub usunąć stary.

Jeśli utworzysz plik wykluczenia za /home/user/rsync_excludepomocą nowego polecenia, będzie to:

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

Tworząc plik listy wykluczeń, każdą regułę wykluczeń należy umieścić w osobnym wierszu. Wyłączenia odnoszą się do katalogu źródłowego. Jeśli /home/user/rsync_excludeplik zawiera następujące opcje:

secret_file
first_dir/subdir/*
second_dir/common_name.*
  • Każdy plik lub katalog wywołany secret_filew katalogu źródłowym zostanie wykluczony.
  • Wszelkie pliki ${source_dir}/first_dir/subdirzostaną wykluczone, ale subdirzsynchronizowana zostanie pusta wersja .
  • Wszelkie pliki ${source_dir}/second_dirz prefiksem common_name.będą ignorowane. Tak common_name.txt, common_name.jpgetc.
Arroniczny
źródło
Nie jestem pewien, czy to robi to, co chciałem. Uważam też, że niepraktyczne jest wyświetlanie wszystkich plików lub folderów, które zostaną dodane do celu. Wolę mieć automatyczny sposób, aby to zrobić. Załóżmy, że mam różne skrypty w celu, które produkują wiele plików dziennika (także w celu) i nie chcę wyszczególniać każdej lokalizacji tych plików w pliku rsync_exclude. Czy istnieje sposób, aby rsync „zapamiętywał”, które pliki zostały zsynchronizowane i pozwolił, aby wpływ na nie miał tylko --delete?
jkrzefski
Przepraszam, źle odczytałem twoje pytanie, ale myślałem, że chcesz dodać do źródła, i nie chcę, aby były one kierowane do celu. Myślę, że jest sposób na zrobienie tego, co chcesz, ale będę musiał się nad tym zastanowić. Skomentuję, gdy będę miał czas na edycję.
Arronical
@jkrzefski Jeśli produkujesz pliki z innego skryptu w celu i chcesz wykluczyć je ze źródła, dlaczego nie zmienić miejsca docelowego tych plików dziennika na inny folder? Przypuszczalnie, jeśli ich nie synchronizujesz, to dlatego, że są mniej ważne.
6

Odkąd wspomniałeś: Nie jestem ograniczony do rsync:

Skrypt do utrzymywania kopii lustrzanej, umożliwiający dodawanie dodatkowych plików do celu

Poniżej skryptu, który robi dokładnie to, co opisujesz.

Skrypt można uruchomić w trybie pełnym (do ustawienia w skrypcie), który wyświetli postęp tworzenia kopii zapasowej (dublowanie). Nie trzeba dodawać, że można to również wykorzystać do rejestrowania kopii zapasowych:

Pełna opcja

wprowadź opis zdjęcia tutaj


Koncepcja

1. Przy pierwszej kopii zapasowej skrypt:

  • tworzy plik (w katalogu docelowym), w którym wymienione są wszystkie pliki i katalogi; .recentfiles
  • tworzy dokładną kopię (kopię lustrzaną) wszystkich plików i katalogów w katalogu docelowym

2. W następnym i tak dalej na kopii zapasowej

  • Skrypt porównuje strukturę katalogów i daty modyfikacji plików. Nowe pliki i katalogi w źródle są kopiowane do kopii lustrzanej. W tym samym czasie tworzony jest drugi (tymczasowy) plik z listą bieżących plików i katalogów w katalogu źródłowym; .currentfiles.
  • Następnie .recentfiles(zestawienie sytuacji na poprzedniej kopii zapasowej) jest porównywane z .currentfiles. Tylko pliki, z .recentfilesktórych nie .currentfilesma, są oczywiście usuwane ze źródła i zostaną usunięte z obiektu docelowego.
  • Pliki dodane ręcznie do folderu docelowego i tak nie są „widoczne” dla skryptu i są pozostawione same sobie.
  • Ostatecznie .currentfilesnazwa tymczasowa zostaje przemianowana .recentfilesna następny cykl tworzenia kopii zapasowych i tak dalej.

Scenariusz

#!/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)

Jak używać

  1. Skopiuj skrypt do pustego pliku i zapisz go jako backup_special.py
  2. Zmień - jeśli chcesz - opcję pełną w nagłówku skryptu:

    # --- choose verbose (or not)
    verbose = True
    # ---
    
  3. Uruchom go jako źródło i cel jako argumenty:

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

Prędkość

Przetestowałem skrypt w katalogu 10 GB z około 40 000 plików i katalogów na moim dysku sieciowym (NAS), dzięki czemu tworzenie kopii zapasowej nastąpiło prawie w tym samym czasie co rsync.

Aktualizacja całego katalogu zajęła tylko kilka sekund więcej niż rsync, na 40 000 plików, co jest niedopuszczalne i nie jest zaskoczeniem, ponieważ skrypt musi porównać zawartość z ostatnio wykonaną kopią zapasową.

Jacob Vlijm
źródło
Cześć @ Aszune'sHeart dodał opcję skryptu. Podaj, czy wszystko jest jasne.
Jacob Vlijm