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
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 .recentfiles
których nie .currentfiles
ma, 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
.currentfiles
nazwa tymczasowa zostaje przemianowana .recentfiles
na 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ć
- Skopiuj skrypt do pustego pliku i zapisz go jako
backup_special.py
Zmień - jeśli chcesz - opcję pełną w nagłówku skryptu:
# --- choose verbose (or not)
verbose = True
# ---
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ą.