Преобразовать идентичные файлы в жесткие ссылки

Преобразовать идентичные файлы в жесткие ссылки

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

Мне приходит в голову, что для значительной части музыки нет никакой разницы между двумя вариантами - обычно, когда распространяемая версия была доступна только в формате mp3/ogg и не имела встроенных обложек. Место на жестком диске может быть дешевым, но это не повод его тратить. Есть ли способ написать скрипт:

  1. Проверьте наличие одинаковых файлов в двух каталогах
  2. При обнаружении идентичных файлов замените один из них жесткой ссылкой на другой.
  3. Не тратя время, например, на получение полной разницы, в интересах экономии времени
  4. Но при этом не возникает риска случайного удаления копии двух неидентичных файлов, что является маловероятным, но ненулевым шансом, если бы я, например, просто сравнивал хэши?

решение1

Ниже приведены примеры использования md5для создания дайджеста MD5 для всех файлов в текущем каталоге или ниже:

find . -type f -exec md5 {} +

Замените md5на md5sum --tag, если у вас нет md5утилиты BSD.

Давайте создадим простой скрипт, который сделает это в каталогах:

#!/bin/bash

tmpdir=${TMPDIR:-/tmp}

if (( $# != 2 )); then
    echo 'Expected two directories as arguments' >&2
    exit 1
fi

i=0
for dir in "$@"; do
    (( ++i ))
    find "$dir" -type f -exec md5 {} + | sort -t '=' -k2 -o "$tmpdir/md5.$i"
done

Это берет два каталога в командной строке и создает файлы с именами md5.1и md5.2, по одному файлу для каждого каталога, в /tmp(или куда $TMPDIRуказывает). Эти файлы сортируются по хэштегу MD5.

Файлы будут выглядеть так

MD5 (<path>) = <MD5 digest>

с одной такой строкой для каждого файла.

Затем в том же скрипте сравните контрольную сумму двух файлов:

join -t '=' -1 2 -2 2 "$tmpdir"/md5.[12]

Это делает реляционную операцию "объединения" между двумя файлами, используя контрольную сумму в качестве поля объединения. Любые строки, имеющие одинаковую контрольную сумму в двух полях, будут объединены и выведены.

Если контрольная сумма в обоих файлах одинакова, будет выведено:

<space><MD5 digest>=MD5 (<path1>) =MD5 (<path2>)

Это можно передать awkнапрямую для анализа двух путей:

awk -F '[()]' 'BEGIN { OFS="\t" } { print $2, $4 }'

Это -F [()]просто способ сказать, что мы хотели бы разделить каждую строку на поля на основе (и ). Сделав это, мы оставим пути в полях 2 и 4.

Это выведет

<path1><tab><path2>

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

while IFS=$'\t' read -r path1 path2; do
    echo ln -f "$path1" "$path2"
done

В итоге:

#!/bin/bash

tmpdir=${TMPDIR:-/tmp}

if (( $# != 2 )); then
    echo 'Expected two directories as arguments' >&2
    exit 1
fi

i=0
for dir in "$@"; do
    (( ++i ))
    find "$dir" -type f -exec md5 {} + | sort -t '=' -k2 -o "$tmpdir/md5.$i"
done

join -t '=' -1 2 -2 2 "$tmpdir"/md5.[12] |
awk -F '\\)|\\(' 'BEGIN { OFS="\t" } { print $2, $4 }' |
while IFS=$'\t' read -r path1 path2; do
    echo ln -f "$path1" "$path2"
done

rm -f "$tmpdir"/md5.[12]

Внутри echoцикла whileесть безопасность. Запустите его один раз, чтобы посмотреть, что произойдет, и удалите его и запустите снова, если вы уверены, что он делает правильно.

Помните, что жесткие ссылки не могут охватывать разделы. Это означает, что оба каталога должны находиться на одном разделе. Файлы ввторойкаталог будет перезаписан, если они окажутся дубликатами. Сохраните где-нибудь резервную копию оригиналов, пока не будете удовлетворены результатом!

Обратите внимание, что это решение не будет работать правильно, если в имени какого-либо файла есть символ (или )или табуляция.

решение2

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

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

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

Связанный контент