rsync – удалить из папки назначения суперсет

У меня есть процесс rsync который синхронизирует содержимое из исходного репозитория (который управляется версией) в общем монтировании NFS.

Сценарий (каким бы ужасным он ни был) заключается в том, что в папке назначения содержится больше содержимого, чем в папке источника, поскольку другой контент синхронизируется с папкой назначения из разных источников. Так, например, структура папок может выглядеть так:

источник

 a/a1.txt a/a2.txt b/b1.txt 

место назначения

 a/a1.txt a/a2.txt a/a3.txt b/b1.txt c/c1.txt 

(в этом примере a/a3.txt и c/c1.txt синхронизируются с пунктом назначения из другого места. На практике это включает несколько других источников, и на содержимое / процессы для них нельзя повлиять.)

Теперь скажите, что исходная папка удаляет файл a/a2.txt . Используя существующую настройку, этот файл не будет удален в месте назначения; но использование --delete приведет к --delete других файлов, и это требование не делать.

Как можно --delete использовать на этом rsync, но выполнить требование? Поскольку исходный каталог управляется версией, достаточно просто получить до и после этого каталога, поэтому можно рассчитать разностную резервную копию, используя исходный исходный каталог в качестве ссылки, но является ли это лучшим способом?

Вы не можете использовать rsync --delete как это. Он не имеет состояния и не хранит записи о том, какие файлы были удалены между запусками. Флаг --delete просто указывает rsync удалить все файлы в месте назначения, которых нет в источнике.

Для реализации этого ограниченного удаления, я думаю, вам нужно поддерживать свое собственное состояние. Ни rsync ни unison могут сделать это для вас.

Следующее не является полным безошибочным решением; это отправная точка. (Тем не менее, он обрабатывает файлы со странными именами – включая те, которые содержат встроенную новую строку.)

Предположим, две директории src и dst . (Для целей примера не имеет значения, является ли dst локальным или удаленным.)

 # Find the current list of files (do this just once, to prep the cache) ( cd src && find . -type f -print0 ) | LC_ALL=C sort -z > .state.src 

Каждый раз, когда мы выполняем резервное копирование, запускаем следующий код

 # Run the rsync to transfer files. "dst/" could be local rsync -av src/ remote:dst/ # Determine the set of files to delete in "dst/" ( cd src && find . -type f -print0 ) | LC_ALL=C sort -z | tee .state.src.new | LC_ALL=C comm -z - -13 .state.src | ssh remote 'while IFS= read -d "" -rf; do rm -f "dst/$f"; done' # That seemed to work, so update the state cache [[ 0 -eq $? ]] && mv -f .state.src.new .state.src 

Если ваша версия comm (как и моя) старше GNU coreutils 8.25 и не имеет флага -z , вы можете использовать этот альтернативный обходной путь:

 # Find the current list of files (do this just once, to prep the cache) ( cd src && find . -type f -print0 ) | tr '\0\n' '\n\0' | LC_ALL=C sort > .state.src 

Каждый раз, когда мы выполняем резервное копирование, запускаем следующий код

 # Run the rsync to transfer files. "dst/" could be local rsync -av src/ remote:dst/ # Determine the set of files to delete in "dst/" ( cd src && find . -type f -print0 ) | tr '\0\n' '\n\0' | LC_ALL=C sort | tee .state.src.new | LC_ALL=C comm -13 - .state.src | tr '\0\n' '\n\0' | ssh remote 'while IFS= read -d "" -rf; do rm -f "dst/$f"; done' # That seemed to work, so update the state cache [[ 0 -eq $? ]] && mv -f .state.src.new .state.src