rsync: フォルダを同期しますが、ターゲットに追加のファイルは保持します

rsync: フォルダを同期しますが、ターゲットに追加のファイルは保持します

私は使い始めたばかりでrsync、ローカル システム上の 2 つのフォルダーを同期させるためにこれを使用しようとしています。時間の経過とともに内容が変化する (一部のファイルが追加され、一部のファイルが変更され、一部のファイルが削除される) ソース フォルダーと、ソースのほぼミラーにしたいターゲット フォルダーがあります。そこで、次のように rsync を使用する方法を試しました。

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

これにより、ターゲットの内容はソースの内容とまったく同じになります。ただし、ソースではなくターゲットにいくつかのファイルを追加できるようにしたいのですが、rsync を実行するたびにそれらのファイルが削除されるのは望ましくありません。一方、以前は同期されていてソースで削除されたファイルは、引き続き削除する必要があります。

除外したいファイルごとにコマンドを変更せずにこれを行う方法はありますか?

アップデート: rsync に限定されているわけではないことを述べておきます。別のプログラムで問題が解決できる場合は、それも問題ありません。私は rsync を使用してこの問題を解決しようとしました。

答え1

rsync--exclude-fromには、除外したいファイルのリストを含むファイルを作成できる optionというオプションがあります。新しい除外を追加したり、古い除外を削除したりするときはいつでも、このファイルを更新できます。

除外ファイルを/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.txtcommon_name.jpgなどです。

答え2

あなたが言及したように:rsyncに限定されません:

ミラーを維持し、ターゲットに追加ファイルを追加できるようにするスクリプト

以下は、まさにあなたが説明したことを実行するスクリプトです。

このスクリプトは、冗長モード(スクリプトで設定)は、バックアップ(ミラーリング)の進行状況を出力します。言うまでもなく、これはバックアップのログ記録にも使用できます。

詳細オプション

ここに画像の説明を入力してください


コンセプト

1. 最初のバックアップでは、スクリプトは次のようになります。

  • すべてのファイルとディレクトリがリストされたファイルを(ターゲット ディレクトリに)作成します。.recentfiles
  • ターゲットディレクトリ内のすべてのファイルとディレクトリの正確なコピー(ミラー)を作成します。

2. 次回以降のバックアップ

  • スクリプトは、ファイルのディレクトリ構造と変更日を比較します。ソース内の新しいファイルとディレクトリはミラーにコピーされます。同時に、ソース ディレクトリ内の現在のファイルとディレクトリをリストする 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>
    

スピード

私は、ネットワーク ドライブ (NAS) 上の約 40,000 個のファイルとディレクトリを含む 10 GB のディレクトリでスクリプトをテストしましたが、rsync とほぼ同じ時間でバックアップが作成されました。

更新中ディレクトリ全体の処理には、40,000 個のファイルで rsync より数秒長いだけかかりました。スクリプトはコンテンツを最後に作成したバックアップと比較する必要があるため、これは許容範囲内であり、驚くことではありません。

関連情報