Bash: поиск и удаление дубликатов файлов из разных папок в зависимости от имени и размера каждого файла - PullRequest
0 голосов
/ 11 февраля 2020

Я слил несколько старых папок mp3 вместе с помощью MusicBrainz Picard https://picard.musicbrainz.org/
Он проделал потрясающую работу по идентификации mp3 и правильно организовал их в структурированную папку, которую я хочу.

Но теперь я нахожу несколько дубликатов в следующем формате:

size    file_path_and_name  
3130248 <artist name>/<artist name> - <song name>.mp3  
3164554 <artist name>/<artist name> - <song name> (1).mp3  
3337687 <artist name>/<artist name> - <song name> (2).mp3  
3130248 <artist name>/<artist name> - <song name> (3).mp3  

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

Мне не нужно иметь несколько копий один и тот же файл для каждого альбома.

Я планировал определить самый большой файл по размеру ( самый большой всегда лучше!;) ) среди дубликатов и удалить остальные.

Я все еще учусь bash, поэтому я решил go шаг на тот момент, но я был заблокирован.
Любая помощь будет оценена.

Вот что у меня так фа r:

#!/bin/bash
tempfile="duplicates.tmp"
tempfilesorted="duplicatessorted.tmp"

# with the following I am identifying every duplicate and create a document with the list
find . -regex '.* ([0-99]).*' > $tempfile
# in $tempfile we would find something like
# ./<artist_1>/<artist_1> - <song_name> (1).mp3
# ./<artist_2>/<artist_2> - <song_name> (1).mp3
# ./<artist_1>/<artist_1> - <song_name> (2).mp3

# I am removing the (xx) numbering
sed -i -e 's/ ([0-99])//g' $tempfile
# in $tempfile we would find something like
# ./<artist_1>/<artist_1> - <song_name>.mp3
# ./<artist_2>/<artist_2> - <song_name>.mp3
# ./<artist_1>/<artist_1> - <song_name>.mp3

# sorting and leave the unique entrances
sort $tempfile | uniq -u > $tempfilesorted
# example of result in $tempfilesorted
# ./<artist_1>/<artist_1> - <song_name>.mp3
# ./<artist_2>/<artist_2> - <song_name>.mp3

# adding * at the end of the file name before the extension
sed -i -r 's/.mp3/*.mp3/' $tempfilesorted
# example of result in $tempfilesorted
# ./<artist_1>/<artist_1> - <song_name>*.mp3
# ./<artist_2>/<artist_2> - <song_name>*.mp3

# my plan from here was simple:
# identify the bigger size and delete the remaining ones
while IFS= read -r line; do
#    ls -S "$line"
    echo "processing: $line"
done <  $tempfilesorted

Любое предложение?


ОБНОВЛЕНИЕ

Благодаря @Aaron я обновил предыдущий сценарий следующим решением :

#!/bin/bash
tempfile="duplicates.tmp"
tempfilesorted="duplicatessorted.tmp"

# with the following I am identifying every duplicate and create a document with the list
find . -regex '.* ([0-99]).*' > $tempfile

# I am removing the (x) numbering
sed -i -e 's/ ([0-9])//g' $tempfile

# sorting and leave the unique entrances
sort $tempfile > $tempfilesorted
# TO DO, I have no idea but the original solution using uniq:
# sort $tempfile | uniq -u > $tempfilesorted
# was skipping few lines, I will find a solution and update

# adding * at the end of the file name before the extension
sed -i -r 's/.mp3/*.mp3/' $tempfilesorted
# TO DO: modify so it works for any type of file, not just mp3

# Identify and rename temporary the bigger file
while IFS= read -r line; do
    echo "processing: $line"
    {
       # read the biggest file from the first line
       IFS=$'\007' read -r biggest_file its_size
       #mv -f "$biggest_file" "${biggest_file/ ([0-9]*).mp3/$'\177'.mp3}"
       mv -f "$biggest_file" "${biggest_file/ ([0-9]*)./OWpfUWNOdC.}"

       # read other files
       while IFS=$'\007' read -r other_file its_size; do
            rm "${other_file}"
       done
    } < <(find . -wholename "$line" -printf '%p\007%s\n' | sort -grt $'\007' -k 2)
done <  $tempfilesorted

# removing the added random string that was used to identify the bigger file
#find -type f -wholename "*OWpfUWNOdC*" -execdir  rename  's/$'\177'.mp3/.mp3/' '{}' \;
find . -type f -wholename "*OWpfUWNOdC*" -execdir  rename  's/OWpfUWNOdC././' '{}' \;

информация о том, почему некоторые поправки от исходного предложенного кода @Aaron:

  1. В следующем сценарии
 size    file_path_and_name  
3130248 <artist name>/<artist name> - <song name>.mp3  
3130248 <artist name>/<artist name> - <song name> (1).mp3 

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

Я изменил find . -name на find . -wholename, потому что в именах файлов были пробелы

По-прежнему происходят странные вещи:

Если я попытаюсь отсортировать и удалить следующий файл, я потеряю несколько входов.
https://pastebin.com/8yvied22
Есть идеи?


помогите с следующее

Как вы можете видеть в коде, я попытался использовать следующий символ $'\177' вместо случайной строки.
Я могу найти файлы, но не могу переименуйте их.
Вы можете найти предложенное мной решение в обновленном коде.
Есть предложения?

Ответы [ 2 ]

0 голосов
/ 13 февраля 2020
size    file_path_and_name  
3130248 <artist name>/<artist name> - <song name>.mp3  
3164554 <artist name>/<artist name> - <song name> (1).mp3  
3337687 <artist name>/<artist name> - <song name> (2).mp3  
3130248 <artist name>/<artist name> - <song name> (3).mp3

определить самый большой файл по размеру (самый большой всегда лучше!;)) Среди дубликатов и удалить остальные.

Я назову <artist name> - <song name>.mp3 как "нормализованный" имя файла "в комментариях ниже. Обратите внимание, что скрипт не будет работать с именами файлов с символами новой строки и \007 байтами.

# print path, filename and size on separate lines
find . -type f -printf '%p\n%f\n%s\n' |
# extract the `<song name> (this number).mp3`
# print 'path, normalized filename, size' separated by 0x07 byte
sed 'N ; s/ ([0-9]*)\(\.[^\.]*\)$/\1/ ; N ; s/\n/\x07/g' |
# sort by normalized filename, then by size !!
sort -t$'\007' -r -k2 -k3n |
tee >(
    # extract the biggest!
    # he will be the first unique normalized filename in second column
    awk -F$'\007' 'prev != $2 {print $1} {prev=$2}' \
        > biggest_files_in_size.txt
) |
# Filter first normalized filenames - ie. "extract the rest"
# So print only if normalized filename is a duplicate since the last line
# outputs paths only
awk -F$'\007' 'prev == $2 {print $1} {prev=$2}' |
# remove those files
# replace `printf "remove: %s\n"` with `rm -v` to really remove those files
xargs -d$'\n' printf "remove: %s\n"

Чтобы действительно удалить файлы, замените printf "remove: %s\n" в последней строке на rm. Со следующим воссозданием файловой структуры:

while IFS=' ' read -r size file; do
    mkdir -p ./"$(dirname "$file")"
    : > "$file"
    dd if=/dev/zero of="$file" bs=1 count="$size" status=none
# two columns - size and filename
done <<'EOF'
3 <artist name>/<artist name> - <song name>.mp3
4 <artist name>/<artist name> - <song name> (1).mp3  
6 <artist name>/<artist name> - <song name> (2).mp3  
5 <artist name>/<artist name> - <song name> (3).mp3  
1 <artist name>/<artist name> - <song name> (123).mp3  
1 Kamil/Kamil - this should stay (3).mp3
2 Kamil/Kamil - remove the 10 (8).mp3
1 Kamil/Kamil - remove the 10 (10).mp3
EOF

Будет распечатано:

remove: ./Kamil/Kamil - remove the 10 (10).mp3
remove: ./<artist name>/<artist name> - <song name> (3).mp3
remove: ./<artist name>/<artist name> - <song name> (1).mp3
remove: ./<artist name>/<artist name> - <song name>.mp3
remove: ./<artist name>/<artist name> - <song name> (123).mp3

И содержимое biggest_files_in_size.txt будет:

./Kamil/Kamil - this should stay (3).mp3
./Kamil/Kamil - remove the 10 (8).mp3
./<artist name>/<artist name> - <song name> (2).mp3

Проверено на реплике .

PS. Каким-то образом в repl sort -t$'\007' -k2 -k3rn производит неправильный порядок сортировки в 3-м столбце, он не меняется Я не знаю почему, это странно, я думаю, что это должно работать. Поэтому я использовал sort -t$'\007' -r -k2 -k3n выше.

0 голосов
/ 12 февраля 2020

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

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

find могут получить размеры файлов, используя соответствующие спецификаторы формата своего действия -printf:

find . -name "$line" -printf '%p\007%s\n'

В этом printf формате %p относится к путь к файлу, %s к его размеру, \n - перевод строки, а \007 - символ ASCII BEL / Bell. Я использую это, потому что я планирую рассматривать строки как состоящие из двух полей, и я должен выбрать символ, который, как я знаю, не будет присутствовать в именах ваших файлов (или их размерах, но это легко), чтобы использовать их в качестве разделителя этих fields.
Стандартом будет использование байта NUL / Null, ASCII 0, но в этом случае я не смог его легко использовать, поэтому любой другой символ, который, я уверен, не будет найден в вашем файле имена делают свое дело. Джеймс Бонд определенно не повлиял на мой выбор.

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

./<artist_1>/<artist_1> - <song_name> (1).mp3?12345
./<artist_1>/<artist_1> - <song_name> (2).mp3?12223  
./<artist_1>/<artist_1> - <song_name>.mp3?12345

(я использовал несвязанную пиктограмму UTF-8 «Колокольчик» для улучшения читабельности, где должно быть \007)

Мне нужно отличить guish строку с наибольшим размером от другие. Одно из решений состоит в том, чтобы отсортировать эти строки численно по второму полю, разделив их числовыми сортировками \007:

sort -grt $'\007' -k 2 

-g, -t $'\007' позволяет нам определить, что наш разделитель полей равен \007, -k 2 определяет второе поле как ключ сортировки, а -r меняет порядок сортировки, что позволяет нам извлечь самый большой файл из первой строки, что проще.

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

{
   # read the biggest file from the first line
   IFS=$'\007' read -r biggest_file its_size
   mv "$biggest_file" $(sed 's/ ([0-9]*).mp3/.mp3/' <<<"$biggest_file")

   # read other files
   while IFS=$'\007' read -r other_file its_size; do
     rm "$other_file"
   done
}

Теперь, чтобы соединить части вместе:

[...]
while IFS= read -r line; do
    echo "processing: $line"

    {
       # read the biggest file from the first line
       IFS=$'\007' read -r biggest_file its_size
       mv "$biggest_file" $(sed 's/ ([0-9]*).mp3/.mp3/' <<<"$biggest_file")

       # read other files
       while IFS=$'\007' read -r other_file its_size; do
         rm "$other_file"
       done
    } < <(find . -name "$line" -printf '%p\007%s\n' | sort -grt $'\007' -k 2)
done <  $tempfilesorted

Обратите внимание, что я не сделал не сосредотачивайтесь на действиях в этом ответе, поскольку я хотел бы развить то, что вы уже сделали, и дать чистый bash ответ, и я ожидаю, что они не будут хорошими. Если он слишком медленный на ваш вкус, вы должны оставить только первый выбор файлов до bash и иметь более высокий уровень языка, такой как awk, python, perl, et c. обрабатывать список файлов.

...