знайти та видалити дублікати в каталозі


12

У мене є каталог з декількома файлами img, і деякі з них ідентичні, але всі вони мають різні назви. Мені потрібно видалити дублікати, але без зовнішніх інструментів лише зі bashскриптом. Я початківець у Linux. Я спробував вкласти для циклу порівняння md5сум і залежно від результату видалення, але щось не так із синтаксисом, і воно не працює. будь-яка допомога?

що я спробував - це ...

for i in directory_path; do
    sum1='find $i -type f -iname "*.jpg" -exec md5sum '{}' \;'
    for j in directory_path; do
        sum2='find $j -type f -iname "*.jpg" -exec md5sum '{}' \;'
        if test $sum1=$sum2 ; then rm $j ; fi
    done
done

Я отримав: test: too many arguments


Будь ласка, включіть у своє запитання будь-які повідомлення про помилки.
terdon

Чому ви не можете використовувати зовнішні інструменти, такі як fdupes? Відповідь @terdon є дивовижною, але вона дійсно підкреслює, чому використовувати хороший інструмент - це шлях, якщо це можливо. Якщо це якесь виділене обладнання або сервер, ви все ще можете мати доступ до нього через мережу тощо з машини, на якій є такі інструменти, як fdupes.
Джо

Відповіді:


28

У вашому сценарії існує досить багато проблем.

  • По- перше, для того , щоб призначити результат виконання команди на змінну ви повинні укласти його або в backtics ( `command`) або, переважно, $(command). Ви маєте його в одиничних лапках ( 'command'), які замість присвоєння результату вашої команди вашій змінній призначає саму команду як рядок. Отже, ваш testнасправді:

    $ echo "test $sum1=$sum2"
    test find $i -type f -iname "*.jpg" -exec md5sum {} \;=find $j -type f -iname "*.jpg" -exec md5sum {} \;
    
  • Наступне питання полягає в тому, що команда md5sumповертає більше, ніж просто хеш:

    $ md5sum /etc/fstab
    46f065563c9e88143fa6fb4d3e42a252  /etc/fstab
    

    Ви хочете лише порівняти перше поле, тому вам слід проаналізувати md5sumвихід, передавши його через команду, яка друкує лише перше поле:

    find $i -type f -iname "*.png" -exec md5sum '{}' \; | cut -f 1 -d ' '

    або

    find $i -type f -iname "*.png" -exec md5sum '{}' \; | awk '{print $1}' 
  • Крім того, findкоманда поверне багато матчів, а не лише одне, і кожне з них буде дублюватися другим find. Це означає, що в якийсь момент ви будете порівнювати той самий файл із самим собою, md5sum буде ідентичним, і ви в кінцевому підсумку видалите всі свої файли (я запустив це на тестовому режимі, що містить a.jpgі b.jpg):

    for i in $(find . -iname "*.jpg"); do
      for j in $(find . -iname "*.jpg"); do
         echo "i is: $i and j is: $j"
      done
    done   
    i is: ./a.jpg and j is: ./a.jpg   ## BAD, will delete a.jpg
    i is: ./a.jpg and j is: ./b.jpg
    i is: ./b.jpg and j is: ./a.jpg
    i is: ./b.jpg and j is: ./b.jpg   ## BAD will delete b.jpg
    
  • Ви не хочете запускатись, for i in directory_pathякщо не передаєте масив каталогів. Якщо всі ці файли в одному каталозі, ви хочете запустити for i in $(find directory_path -iname "*.jpg"), щоб пройти всі файли.

  • Дуже погано використовувати forпетлі з результатом знаходження. Ви повинні використовувати whileпетлі або глобус :

    find . -iname "*.jpg" | while read i; do [...] ; done

    або, якщо всі ваші файли знаходяться в одному каталозі:

    for i in *jpg; do [...]; done

    Залежно від вашої оболонки та параметрів, які ви встановили, ви можете використовувати глобулінг навіть для файлів у підкаталогах, але не будемо тут вникати.

  • Нарешті, ви також повинні навести ваші змінні шляхи до каталогу, які містять пробіли, розбивають ваш сценарій.

Імена файлів можуть містити пробіли, нові рядки, зворотні косої риси та інші дивні символи, щоб правильно з ними працювати в whileциклі, вам потрібно буде додати ще кілька варіантів. Те, що ви хочете написати, - це щось на зразок:

find dir_path -type f -iname "*.jpg" -print0 | while IFS= read -r -d '' i; do
  find dir_path -type f -iname "*.jpg" -print0 | while IFS= read -r -d '' j; do
    if [ "$i" != "$j" ]
    then
      sum1=$(md5sum "$i" | cut -f 1 -d ' ' )
      sum2=$(md5sum "$j" | cut -f 1 -d ' ' )
      [ "$sum1" = "$sum2" ] && rm "$j"
    fi
  done
done

Ще простішим способом було б:

find directory_path -name "*.jpg" -exec md5sum '{}' + | 
 perl -ane '$k{$F[0]}++; system("rm $F[1]") if $k{$F[0]}>1'

Краща версія, яка може мати пробіли у назвах файлів:

find directory_path -name "*.jpg" -exec md5sum '{}' + | 
 perl -ane '$k{$F[0]}++; system("rm \"@F[1 .. $#F]\"") if $k{$F[0]}>1'

Цей маленький скрипт Perl запустить результати findкоманди (тобто md5sum та ім'я файлу). -aВаріант для perlрозколів вхідних ліній пробільних і зберігає їх в Fмасиві, так $F[0]буде md5sum і $F[1]ім'я файлу. Md5sum зберігається в хеші, kі скрипт перевіряє, чи хеш уже бачили ( if $k{$F[0]}>1), і видаляє файл, якщо він має ( system("rm $F[1]")).


Хоча це працюватиме, для великих колекцій зображень буде дуже повільно, і ви не можете вибрати, які файли зберігати. Існує багато програм, які вирішують цю проблему більш елегантно, зокрема:


+1 для фрагмента Perl Дійсно елегантний! Ви також можете використовувати власний Perl unlinkзамість того, щоб systemтелефонувати.
Джозеф Р.

@JosephR. Дякую :). Хоча помилка, вона не зможе отримати імена файлів з пробілами, оскільки тільки перші символи імені до першого пробілу будуть у $F[1]. Виправлено за допомогою фрагментів масиву. Щодо unlink () я знаю, але хотів звести перлізми до мінімуму, і системний виклик простіше зрозуміти, якщо ви не знаєте Perl.
terdon

13

Існує чудова програма, яка називається, fdupesяка спрощує весь процес і спонукає користувача до видалення дублікатів. Я думаю, що варто перевірити:

$ fdupes --delete DIRECTORY_WITH_DUPLICATES
[1] DIRECTORY_WITH_DUPLICATES/package-0.1-linux.tar.gz        
[2] DIRECTORY_WITH_DUPLICATES/package-0.1-linux.tar.gz.1

Set 1 of 1, preserve files [1 - 2, all]: 1

   [+] DIRECTORY_WITH_DUPLICATES/package-0.1-linux.tar.gz
   [-] DIRECTORY_WITH_DUPLICATES/package-0.1-linux.tar.gz.1

В основному, це запропонувало мені, який файл зберігати , я набрав 1 , а він видалив другий.

Інші цікаві варіанти:

-r --recurse
    for every directory given follow subdirectories encountered within

-N --noprompt
    when used together with --delete, preserve the first file in each set of duplicates and delete the others without prompting the user

З вашого прикладу ви, ймовірно, хочете запустити його як:

fdupes --recurse --delete --noprompt DIRECTORY_WITH_DUPLICATES

Перегляньте man fdupesвсі доступні варіанти.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.