web-dev-qa-db-fra.com

rsync: synchronise les dossiers, mais conserve les fichiers supplémentaires dans la cible

Je commence à utiliser rsync et j'essaie de l'utiliser pour garder deux dossiers synchronisés sur le système local. J'ai un dossier source, dont le contenu change au fil du temps (certains fichiers sont ajoutés, certaines modifications et d'autres supprimées) et un dossier cible que je veux presque faire un miroir de la source. Donc ce que j'ai essayé utilisait rsync comme ceci:

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

Cela permet de garder le contenu de la cible exactement identique au contenu de la source. Cependant, j'aimerais pouvoir ajouter des fichiers à la cible et non à la source, mais je ne veux pas qu'ils soient supprimés à chaque fois que je fais rsync. D'autre part, les fichiers qui étaient synchronisés et ensuite supprimés dans le source doivent toujours l'être.

Y a-t-il un moyen de le faire sans avoir à modifier la commande pour chaque fichier que je veux exclure?

Mise à jour: Je devrais mentionner que je ne suis pas limité à rsync. Si un autre programme fait le travail, c'est bien aussi. J'ai juste essayé de résoudre cela en utilisant rsync.

10
jkrzefski

rsync dispose d'une option appelée --exclude-from qui permet de créer un fichier contenant une liste des fichiers que vous souhaitez exclure. Vous pouvez mettre à jour ce fichier chaque fois que vous souhaitez ajouter une nouvelle exclusion ou en supprimer une ancienne.

Si vous créez le fichier d'exclusion sous /home/user/rsync_exclude, la nouvelle commande serait:

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

Lors de la création du fichier de liste d'exclusion, vous devez placer chaque règle d'exclusion sur une ligne distincte. Les exclusions sont relatives à votre répertoire source. Si votre fichier /home/user/rsync_exclude contient les options suivantes:

secret_file
first_dir/subdir/*
second_dir/common_name.*
  • Tout fichier ou répertoire appelé secret_file dans votre répertoire source sera exclu.
  • Tous les fichiers de ${source_dir}/first_dir/subdir seront exclus, mais une version vide de subdir sera synchronisée.
  • Tous les fichiers de ${source_dir}/second_dir avec le préfixe common_name. seront ignorés. Donc, common_name.txt, common_name.jpg etc.
9
Arronical

Puisque vous avez mentionné: , je ne suis pas limité à rsync:

Script pour maintenir le miroir, permettant d'ajouter des fichiers supplémentaires à la cible

Ci-dessous un script qui fait exactement ce que vous décrivez.

Le script peut être exécuté en mode verbose (à définir dans le script), qui affichera la progression de la sauvegarde (mise en miroir). Inutile de dire que cela peut également être utilisé pour enregistrer les sauvegardes:

Option détaillée

enter image description here


Le concept

1. Sur la première sauvegarde, le script:

  • crée un fichier (dans le répertoire cible), dans lequel tous les fichiers et répertoires sont répertoriés; .recentfiles
  • crée une copie exacte (miroir) de tous les fichiers et répertoires du répertoire cible

2. Sur la sauvegarde suivante et ainsi de suite

  • Le script compare la structure du répertoire et la date de modification des fichiers. Les nouveaux fichiers et répertoires de la source sont copiés dans le miroir. En même temps, un deuxième fichier (temporaire) est créé, répertoriant les fichiers et répertoires actuels dans le répertoire source; .currentfiles.
  • Par la suite, .recentfiles (répertoriant la situation sur la sauvegarde précédente) est comparé à .currentfiles. Seuls les fichiers de .recentfiles qui ne figurent pas dans .currentfiles sont évidemment supprimés de la source, et sera retiré de la cible.
  • Les fichiers que vous avez ajoutés manuellement au dossier cible ne sont en aucun cas "vus" par le script et sont laissés seuls.
  • Enfin, le .currentfiles temporaire est renommé en .recentfiles pour servir le prochain cycle de sauvegarde, etc.

Le scénario

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

Comment utiliser

  1. Copiez le script dans un fichier vide, enregistrez-le sous le nom backup_special.py
  2. Changez, si vous le souhaitez, l’option verbeuse dans l’en-tête du script:

    # --- choose verbose (or not)
    verbose = True
    # ---
    
  3. Exécutez-le avec les arguments source et target:

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

La vitesse

J'ai testé le script sur un répertoire de 10 Go contenant environ 40 000 fichiers et répertoires sur mon lecteur réseau (NAS). La sauvegarde a été effectuée pratiquement en même temps que rsync.

La mise à jour de l'ensemble du répertoire n'a pris que quelques secondes de plus que rsync, sur 40 000 fichiers, ce qui est inacceptable et sans surprise, car le script doit comparer le contenu à la dernière sauvegarde effectuée. .

6
Jacob Vlijm