bir dizindeki kopyaları bulma ve kaldırma


12

Birden çok img dosyası olan bir dizin var ve bazıları aynı ama hepsi farklı isimleri var. Yinelenenleri kaldırmanız gerekiyor, ancak yalnızca bir bashkomut dosyasıyla harici araçlar kullanmıyorum. Linux'a yeni başlayan biriyim. md5Toplamları karşılaştırmak için döngü için iç içe çalıştım ve sonuca bağlı olarak ama sözdizimi ile yanlış bir şey var ve çalışmıyor. herhangi bir yardım?

denediğim şey ...

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

Alırım: test: too many arguments


Lütfen sorunuzla ilgili olarak aldığınız hata mesajlarını da ekleyin.
terdon

Neden fdupes gibi harici araçlar kullanmıyorsunuz? @terdon'ın cevabı şaşırtıcı, ama iyi bir araç kullanmanın neden mümkünse yol olduğunu gerçekten vurgulamaktadır. Bir çeşit özel donanım veya sunucu ise, fdupes gibi araçlara sahip bir makineden bir ağ vb. Üzerinden erişmeye devam edebilirsiniz.
Joe

Yanıtlar:


28

Senaryonuzda birkaç problem var.

  • İlk olarak , bir komutun sonucunu bir değişkene atamak için , onu backtics ( `command`) veya tercihen, içine almanız gerekir $(command). 'command'Komutunuzun sonucunu değişkeninize atamak yerine, komutun kendisini dize olarak atayan tek tırnak işareti ( ) kullanırsınız. Bu nedenle, testaslında:

    $ echo "test $sum1=$sum2"
    test find $i -type f -iname "*.jpg" -exec md5sum {} \;=find $j -type f -iname "*.jpg" -exec md5sum {} \;
  • Bir sonraki sorun, komutun md5sumkarma değerinden fazlasını döndürmesidir:

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

    Yalnızca ilk alanı karşılaştırmak istiyorsunuz, bu nedenle md5sumçıktıyı yalnızca ilk alanı yazdıran bir komuttan geçirerek ayrıştırmalısınız :

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

    veya

    find $i -type f -iname "*.png" -exec md5sum '{}' \; | awk '{print $1}' 
  • Ayrıca, findkomut birçok eşleşmeyi döndürür, yalnızca bir tane değil, bu eşleşmelerin her biri ikincisi tarafından çoğaltılır find. Bir noktada kendisine aynı dosyayı karşılaştırarak olacağını Bu araçlar, md5sum özdeş olacak ve silme sona erecek tüm dosyalarınızı (ı içeren bir test dir bu koştum a.jpgve 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_pathBir dizi dizini geçmediğiniz sürece çalıştırmak istemezsiniz . Tüm bu dosyalar aynı dizindeyse, çalıştırmak istediğiniz for i in $(find directory_path -iname "*.jpg") tüm dosyaları gözden geçirin.

  • Öyle kötü bir fikir kullanmak forfind çıkışı ile döngüler. whileDöngüler veya globbing kullanmalısınız :

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

    veya tüm dosyalarınız aynı dizindeyse:

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

    Kabuğunuza ve belirlediğiniz seçeneklere bağlı olarak, alt dizinlerdeki dosyalar için bile globbing kullanabilirsiniz, ancak buraya girmeyelim.

  • Son olarak, değişkenlerinizi başka yerlere de eklemelisiniz. Boşluklu dizin yolları betiğinizi bozacaktır.

Dosya adları boşluklar, yeni satırlar, ters eğik çizgiler ve diğer garip karakterler içerebilir, bir whiledöngüde doğru olanlarla başa çıkmak için biraz daha seçenek eklemeniz gerekir. Yazmak istediğiniz şey şudur:

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

Daha da basit bir yol:

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

Dosya adlarındaki boşluklarla başa çıkabilen daha iyi bir sürüm:

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

Bu küçük Perl betiği findkomutun sonuçları üzerinden çalışacaktır (md5sum ve dosya adı gibi). -aSeçeneği perlboşluktan böler girdi hatları ve bunları kaydeder Fböylece, dizinin $F[0]md5sum ve olacaktır $F[1]dosya adı. Md5sum karmaya kaydedilir kve komut dosyası karmanın daha önceden göründüğünü ( if $k{$F[0]}>1) kontrol eder ve ( ) varsa dosyayı siler system("rm $F[1]").


Bu işe yarayacak olsa da, büyük resim koleksiyonları için çok yavaş olacaktır ve hangi dosyaların saklanacağını seçemezsiniz. Bunu daha zarif bir şekilde ele alan birçok program vardır:


Perl snippet'i için +1. Gerçekten zarif! Telefon etmek unlinkyerine Perl'inkini de kullanabilirsiniz system.
Joseph R.

@JosephR. Teşekkürler :). Yine de bir hata vardı, sadece ilk alana kadar bir adın ilk karakterleri olacağı için boşluklu dosya adları için başarısız olur $F[1]. Dizi dilimleri kullanarak düzeltildi. Unlink () gelince biliyorum, ama perlisms minimum tutmak istedim ve Perl bilmiyorsanız sistem çağrısı anlamak daha kolaydır.
terdon

13

fdupesTüm süreci basitleştiren ve kullanıcıdan kopyaları silmesini isteyen şık bir program var . Kontrol etmeye değer olduğunu düşünüyorum:

$ 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

Temelde, hangi dosya için beni istenir tutmak ben daktilo, 1 ve ikinci kaldırıldı.

Diğer ilginç seçenekler:

-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

Örneğinizden, muhtemelen şu şekilde çalıştırmak istersiniz:

fdupes --recurse --delete --noprompt DIRECTORY_WITH_DUPLICATES

man fdupesMevcut tüm seçenekler için bakınız .

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.