Любой способ синхронизации структуры каталогов, когда файлы уже с обеих сторон?

У меня два диска с одинаковыми файлами, но структура каталогов совершенно другая.

Есть ли способ «переместить» все файлы на стороне назначения, чтобы они соответствовали структуре исходной стороны? Возможно, сценарий?

Например, диск A имеет:

/foo/bar/123.txt /foo/bar/234.txt /foo/bar/dir/567.txt 

В то время как диск B имеет:

 /some/other/path/123.txt /bar/doo2/wow/234.txt /bar/doo/567.txt 

Файлы, о которых идет речь, огромны (800 ГБ), поэтому я не хочу их повторно копировать; Я просто хочу синхронизировать структуру, создав необходимые каталоги и перемещая файлы.

Я думал о рекурсивном скрипте, который найдет каждый исходный файл в пункте назначения, а затем переместит его в соответствующий каталог, создав его, если это необходимо. Но … это невозможно!

Другое элегантное решение было дано здесь: https://superuser.com/questions/237387/any-way-to-sync-directory-structure-when-the-files-are-already-on-both-sides/238086

8 Solutions collect form web for “Любой способ синхронизации структуры каталогов, когда файлы уже с обеих сторон?”

Я поеду с Жилем и укажу на Unison, как это было предложено Hasen j . Unison был DropBox за 20 лет до DropBox. Скальный твердый код, который многие люди (включая меня) используют каждый день – очень полезно учиться. Тем не менее, join требует всей рекламы, которую он может получить 🙂


Это всего лишь половина ответа, но мне нужно вернуться к работе 🙂

В принципе, я хотел продемонстрировать малоизвестную утилиту join которая делает именно это: объединяет две таблицы в некотором поле.

Сначала настройте тестовый пример, включающий имена файлов с пробелами:

 for d in ab 'c c'; do mkdir -p "old/$d"; echo $RANDOM > "old/${d}/${d}.txt"; done cp -r old new 

(отредактируйте какой-либо каталог и / или имена файлов в new ).

Теперь мы хотим создать карту: hash -> filename для каждого каталога, а затем использовать join для сопоставления файлов с одинаковым хэшем. Чтобы создать карту, makemap.sh следующее:

 find "$1" -type f -exec md5 -r "{}" \; \ | sed "s/\([a-z0-9]*\) ${1}\/\(.*\)/\1 \"\2\"/" \ 

makemap.sh выплескивает файл с строками формы, «hash» filename », поэтому мы просто присоединяемся к первому столбцу:

 join <(./makemap.sh 'old') <(./makemap.sh 'new') >moves.txt 

Это создает moves.txt который выглядит так:

 49787681dd7fcc685372784915855431 "a/a.txt" "bar/a.txt" bfdaa3e91029d31610739d552ede0c26 "cc/c c.txt" "cc/c c.txt" 

Следующим шагом было бы на самом деле сделать ходы, но мои попытки застряли при цитировании … mv -i и mkdir -p должны пригодиться.

Утилита называется унисон:

http://www.cis.upenn.edu/~bcpierce/unison/

Описание с сайта:

Unison – это инструмент синхронизации файлов для Unix и Windows. Он позволяет хранить две копии коллекций файлов и каталогов на разных хостах (или разных дисках на одном и том же хосте), изменяться отдельно, а затем обновляться, распространяя изменения каждой реплики на другую.

Обратите внимание, что Unison только обнаруживает перемещенные файлы при первом запуске, если хотя бы один из корней удален, поэтому, даже если вы синхронизируете локальные файлы, используйте ssh://localhost/path/to/dir как один из корней.

Используйте Unison, как предложено hasen j . Я оставляю этот ответ как потенциально полезный пример сценариев или для использования на сервере с установленными базовыми утилитами.


Я предполагаю, что имена файлов уникальны по всей иерархии. Я также предполагаю, что ни одно имя файла не содержит новую строку, и что деревья каталога содержат только каталоги и обычные файлы.

  1. Сначала соберите имена файлов со стороны источника.

     (cd /A && find . \! -type d) >A.find 
  2. Затем переместите файлы на место со стороны адресата. Сначала создайте сплющенное дерево файлов на стороне назначения. Используйте ln вместо mv если вы хотите сохранить жесткие ссылки в старой иерархии.

     mkdir /B.staging /B.new find /B.old -type f -exec sh -c 'mv -- "$@" "$0"' /B.staging {} + 
  3. Если некоторые файлы могут отсутствовать в месте назначения, создайте аналогично сплющенное / /A.staging и используйте rsync для копирования данных из источника в пункт назначения.

     rsync -au /A.staging/ /B.staging/ 
  4. Теперь переименуйте файлы на место.

     cd /B.new && <A.find perl -l -ne ' my $dir = '.'; s!^\./+!!; while (s!^([^/]+)/+!!) { # Create directories as needed $dir .= "/$1"; -d $dir or mkdir $dir or die "mkdir $dir: $!" } rename "/B.staging/$_", "$dir/$_" or die "rename -> $dir/$_: $!" ' 

    Эквивалентное:

     cd /B.new && <A.find python -c ' import os, sys for path in sys.stdin.read().splitlines(): dir, base = path.rsplit("/", 2) os.rename(os.path.join("/B.new", base), path) ' 
  5. Наконец, если вы заботитесь о метаданных каталогов, вызовите rsync с уже существующими файлами.

     rsync -au /A/ /B.new/ 

Обратите внимание, что я не тестировал фрагменты в этом сообщении. Используйте на свой риск. Сообщите об ошибке в комментарии.

Как насчет чего-то вроде этого:

 src=/mnt/driveA dst=/mnt/driveB cd $src find . -name <PATTERN> -type f >/tmp/srclist cd $dst find . -name <PATTERN> -type f >/tmp/dstlist cat /tmp/srclist | while read srcpath; do name=`basename "$srcpath"` srcdir=`dirname "$srcpath"` dstpath=`grep "/${name}\$" /tmp/dstlist` mkdir -p "$srcdir" cd "$srcdir" && ln -s "$dstpath" "$name" done 

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

Сценарий выше будет работать в простых случаях, но может завершиться ошибкой, если name содержит символы, которые имеют особое значение для регулярных выражений. grep в списке файлов также может занимать много времени, если есть много файлов. Вы можете перевести этот код на использование хеш-таблицы, которая будет отображать имена файлов в пути, например, в Ruby.

В частности, если бы текущая синхронизация была бы полезна, вы могли бы попытаться выяснить git-приложение .

Это относительно новое; Я не пытался использовать его сам.

Я могу предложить это, потому что он избегает сохранения второй копии файлов … это означает, что он должен отмечать файлы как доступные для чтения («заблокированные»), например, определенные системы контроля версий, отличные от Git.

Файлы идентифицируются по расширению файла sha256sum + (по умолчанию). Таким образом, он должен иметь возможность синхронизировать два репозитория с идентичным содержимым файла, но с разными именами файлов, без необходимости выполнять записи (и в случае сети с низкой пропускной способностью, если это необходимо). Разумеется, конечно, нужно будет прочитать все файлы, чтобы их проверить.

Вот моя попытка ответить. Как предупреждение, весь мой опыт работы с скриптами исходит от bash, поэтому, если вы используете другую оболочку, имена команд или синтаксис могут быть разными.

Это решение требует создания двух отдельных скриптов.

Этот первый скрипт отвечает за фактическое перемещение файлов на целевом диске.

 md5_map_file="<absolute-path-to-a-temporary-file>" # Given a single line from the md5 map file, list # only the path from that line. get_file() { echo $2 } # Given an md5, list the filename from the md5 map file get_file_from_md5() { # Grab the line from the md5 map file that has the # md5 sum passed in and call get_file() with that line. get_file `cat $md5_map_file | grep $1` } file=$1 # Compute the md5 sum=`md5sum $file` # Get the new path for the file new_file=`get_file_from_md5 $sum` # Make sure the destination directory exists mkdir -p `dirname $new_file` # Move the file, prompting if the move would cause an overwrite mv -i $file $new_file 

Второй скрипт создает файл карты md5, используемый первым скриптом, а затем вызывает первый скрипт для каждого файла на целевом диске.

 # Do not put trailing / src="<absolute-path-to-source-drive>" dst="<absolute-path-to-destination-drive>" script_path="<absolute-path-to-the-first-script>" md5_map_file="<same-absolute-path-from-first-script>" # This command searches through the source drive # looking for files. For every file it finds, # it computes the md5sum and writes the md5 sum and # the path to the found filename to the filename stored # in $md5_map_file. # The end result is a file listing the md5 of every file # on the source drive cd $src find . -type f -exec md5sum "{}" \; > $md5_map_file # This command searches the destination drive for files and calls the first # script for every file it finds. cd $dst find . -type f -exec $script_path '{}' \; 

В основном, что происходит, два сценария похожи на ассоциативный массив с $md5_map_file . Во-первых, все md5s для файлов на исходном диске вычисляются и сохраняются. Связанными с md5 являются относительные пути от корня диска. Затем для каждого файла на целевом диске вычисляется md5. Используя этот md5, путь к этому файлу на исходном диске просматривается. Затем файл на целевом диске перемещается в соответствии с файлом на исходном диске.

Есть пара предостережений с этим скриптом:

  • Предполагается, что каждый файл в $ dst также находится в $ src
  • Он не удаляет каталоги из $ dst, а только перемещает файлы. В настоящее время я не могу думать о безопасном способе сделать это автоматически

Удачи, и я надеюсь, что это помогло.

Предполагая, что базовые имена файлов уникальны в деревьях, это довольно просто:

 join <(cd A; find . -type f | while read f; do echo $(basename $f) $(dirname $f); done | sort) \ <(cd B; find . -type f | while read f; do echo $(basename $f) $(dirname $f); done | sort) |\ while read name to from do mkdir -p B/$to mv -v B/$from/$name B/$to/ done 

Если вы хотите очистить старые пустые каталоги, используйте:

 find B -depth -type d -delete 

Я также столкнулся с этой проблемой. Решение на базе md5sum не работало для меня, потому что я синхронизовал свои файлы с монтировкой webdav . Вычисление сумм md5sum в пункте назначения webdav также означает большие операции с файлами.

Я сделал небольшой скрипт reorg_Remote_Dir_detect_moves.sh (на github), который пытается обнаружить наиболее перемещенные файлы, а затем создает новый временный shell-скрипт с несколькими командами для настройки удаленного каталога. Поскольку я только забочусь о именах файлов, сценарий не является идеальным решением.

Для безопасности несколько файлов будут проигнорированы: A) Файлы с одинаковыми (одинаковыми начальными) именами со всех сторон и B) Файлы, которые находятся только на удаленной стороне. Они будут проигнорированы и пропущены.

Затем пропущенные файлы будут обрабатываться вашим предпочтительным инструментом синхронизации (например, rsync, unison , …), который вы должны использовать после запуска временного сценария оболочки.

Так может быть, мой сценарий полезен для кого-то? Если это так (чтобы сделать это более понятным), есть три шага:

  1. Запустите сценарий оболочки reorg_Remote_Dir_detect_moves.sh (на github)
  2. Это создаст временный shell-script /dev/shm/REORGRemoteMoveScript.sh => запустите это, чтобы сделать ходы (будет быстро на смонтированном webdav )
  3. Запустите свой предпочтительный инструмент синхронизации (например, rsync, unison , …)
  • Синхронизация миллионов файлов между двумя серверами Linux
  • Подключение к часам через Bluetooth
  • SyncEvolution: «Сравнение было невозможно», и никакая синхронизация CardDav больше
  • Есть ли инструмент, который подключается к двум каталогам и синхронизирует их, как только что-то меняется?
  • Синхронизация файлов в реальном времени
  • Сетевой файл копирования с низкими накладными расходами
  • Исключить папку из удаления при поиске дубликатов с помощью «fdupes»
  • Как я могу совместно использовать профиль thunderbird (icedove) в режиме реального времени между двумя компьютерами?
  • Общий доступ к sysfs
  • Синхронизация нескольких компьютеров с Unison
  • Как выполнить автоматическую синхронизацию с подключенным запоминающим устройством USB?
  • Linux и Unix - лучшая ОС в мире.