У меня много музыки в дереве в одном каталоге, сохраненной в том формате, в котором я изначально ее получил, для качества. У меня есть второе дерево каталогов, которое похоже по структуре, но со всеми файлами в сжатом формате, воспроизводимом моим телефоном, и с периодическими изменениями метаданных (например, удалением встроенных обложек для экономии места).
Мне приходит в голову, что для значительной части музыки нет никакой разницы между двумя вариантами - обычно, когда распространяемая версия была доступна только в формате mp3/ogg и не имела встроенных обложек. Место на жестком диске может быть дешевым, но это не повод его тратить. Есть ли способ написать скрипт:
- Проверьте наличие одинаковых файлов в двух каталогах
- При обнаружении идентичных файлов замените один из них жесткой ссылкой на другой.
- Не тратя время, например, на получение полной разницы, в интересах экономии времени
- Но при этом не возникает риска случайного удаления копии двух неидентичных файлов, что является маловероятным, но ненулевым шансом, если бы я, например, просто сравнивал хэши?
решение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
Если у вас нет большой коллекции очень похожих файлов, вычисление и сравнение хэшей не ускоряет процесс поиска дубликатов. Самая медленная операция — чтение с диска. Вычисление хеша означает чтение всего файла, а также это задача, интенсивно использующая процессор с современными криптографически сильными хэшами.
Нам нужно сравнивать данные только в том случае, если длина файлов отличается. Если есть только один файл с заданной длиной, то, очевидно, нет дубликатов. Если их два, простое сравнение всегда эффективнее хеширования. Если их три или больше, количество сравнений увеличивается, но есть вероятность, что они различаются в первых байтах или блоках, поэтому дисковый ввод-вывод все еще низок, и повторные чтения возвращаются из кэша.
Вот почему я бы рекомендовал сделать рекурсивный список каталогов, подготовив список длина+путь, затем отсортировать список по номерам и, наконец, иметь дело только с наборами файлов, разделяющими одинаковую длину, сравнивая их попарно. Если два файла совпадают, один из них можно заменить жесткой ссылкой.