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


24

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

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

Например, диск 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 ГБ), поэтому я не хочу их заново копировать; Я просто хочу синхронизировать структуру путем создания необходимых каталогов и перемещения файлов.

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

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


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

Ответы:


11

Я пойду с Жилем и укажу на Унисон, как это предложил Хасен Дж . Унисон был DropBox за 20 лет до DropBox. Рок твердый код, который многие люди (включая меня) используют каждый день - очень стоит изучить. Тем не менее, joinнужна вся реклама, которую он может получить :)


Это только половина ответа, но я должен вернуться к работе :)

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

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

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

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

Теперь мы хотим построить карту: хэш -> имя файла для каждого каталога, а затем использовать 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 "c c/c c.txt" "c c/c c.txt"

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


Извините, я ничего этого не понимаю!
Дан

1
joinдействительно интересно. Спасибо, что обратили на это мое внимание.
Стивен Д.

@Dan. Сожалею. Проблема в том, что я не знаю, какие предположения я могу сделать относительно имен ваших файлов. Сценарии без допущений неинтересны, особенно в этом случае, когда я решил вывести имена файлов в файл dwheeler.com/essays/fixing-unix-linux-filenames.html .
Янус

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

Вам не нужно сортировать файлы, которые вы используете в качестве joinвходных данных?
CJM

8

Есть утилита под названием unison:

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

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

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

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


@ Жиль: Ты уверен? Я использую унисон для всего и часто вижу, что он обнаруживает файлы, которые были переименованы и / или удалены. Вы говорите, что это работает только для уже синхронизированных файлов, где у unison была возможность записать номера inode (или любые другие приемы, которые он использует)?
Янус

@Janus: Спасибо за исправление, мой комментарий был действительно неправильным. Unison обнаруживает файлы, которые были перемещены, даже при первом запуске. (Это не происходит, когда оба корня являются локальными, поэтому в моем тесте этого не было.) Так что унисон - очень хорошее предложение.
Жиль "ТАК - перестань быть злым"

@Gilles. Полезно знать - кажется, что существует довольно много мест, где алгоритм различает локальную и удаленную синхронизацию. Я действительно не думал, что это сработает для первой синхронизации. +1 за унисон!
Янус

4

Используйте 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/

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


2

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

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

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

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


1

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

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.


Это выглядит многообещающе - но перемещает ли файлы или просто создает символические ссылки?
Дан

Я думаю, что понимаю большую часть этого; но что делает grepлиния? Он просто находит полный путь к соответствующему файлу в dstlist?
Дан

@Dan: по-видимому, с помощью lnэтого создает символические ссылки. Вы можете использовать mvдля перемещения файлов, но остерегайтесь перезаписи существующих. Кроме того, вы можете захотеть очистить пустые каталоги, если они есть, после удаления файлов. Да, эта grepкоманда ищет строку, которая заканчивается на имени файла, таким образом показывая полный путь к нему на целевом диске.
Алекс

1

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

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

1

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

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

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

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

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

  1. Запустите скрипт оболочки reorg_Remote_Dir_detect_moves.sh (на github)
  2. Это создаст временный shell-скрипт /dev/shm/REORGRemoteMoveScript.sh=> запустить его, чтобы сделать ходы (будет быстро при подключении webdav)
  3. Запустите предпочитаемый инструмент синхронизации (например rsync, unison, ...)

1

Вот моя попытка ответа. Как предупреждение, весь мой опыт написания сценариев происходит от 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 для файлов на исходном диске вычисляются и сохраняются. С md5s связаны относительные пути от корня диска. Затем для каждого файла на целевом диске вычисляется md5. Используя этот md5, ищется путь к этому файлу на исходном диске. Затем файл на целевом диске перемещается в соответствии с путем к файлу на исходном диске.

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

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

Для вычисления md5 требуется много времени: весь контент действительно должен быть прочитан. Хотя Дэн уверен, что файлы идентичны, просто переместить их в структуру каталогов очень быстро (без чтения). Так что, md5sumпохоже, не то, что здесь нужно использовать. (Кстати, rsyncесть режим, в котором он не вычисляет контрольные суммы.)
imz - Иван Захарящев

Это компромисс между точностью и скоростью. Я хотел предоставить метод, который использовал бы более высокую степень точности, чем просто имена файлов.
cledoux
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.