temukan dan hapus duplikat di direktori


12

Saya memiliki direktori dengan banyak file img dan beberapa di antaranya identik tetapi semuanya memiliki nama yang berbeda. Saya perlu menghapus duplikat tetapi tanpa alat eksternal hanya dengan bashskrip. Saya seorang pemula di Linux. Saya mencoba bersarang untuk loop untuk membandingkan md5jumlah dan tergantung pada hasil hapus tetapi ada sesuatu yang salah dengan sintaks dan tidak berfungsi. ada bantuan?

apa yang saya coba adalah ...

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

Saya mendapat: test: too many arguments


Harap sertakan juga pesan kesalahan yang Anda dapatkan di pertanyaan Anda.
terdon

Mengapa Anda tidak dapat menggunakan alat eksternal seperti fdupes? Jawaban @terdon luar biasa, tetapi itu benar-benar menyoroti mengapa menggunakan alat yang baik adalah cara untuk pergi jika memungkinkan. Jika itu semacam perangkat keras atau server khusus, Anda mungkin masih dapat mengaksesnya melalui jaringan, dll. Dari mesin yang memang memiliki alat seperti fdupes yang tersedia.
Joe

Jawaban:


28

Ada beberapa masalah dalam skrip Anda.

  • Pertama, untuk menetapkan hasil perintah ke variabel, Anda harus menyertakannya dalam backtics ( `command`) atau, lebih disukai $(command),. Anda memilikinya dalam tanda kutip tunggal ( 'command') yang alih-alih menetapkan hasil perintah Anda ke variabel Anda, tetapkan perintah itu sendiri sebagai string. Karena itu, testsebenarnya Anda:

    $ echo "test $sum1=$sum2"
    test find $i -type f -iname "*.jpg" -exec md5sum {} \;=find $j -type f -iname "*.jpg" -exec md5sum {} \;
  • Masalah berikutnya adalah bahwa perintah md5summengembalikan lebih dari sekedar hash:

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

    Anda hanya ingin membandingkan bidang pertama, jadi Anda harus mem-parsing md5sumkeluaran dengan meneruskannya melalui perintah yang hanya mencetak bidang pertama:

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

    atau

    find $i -type f -iname "*.png" -exec md5sum '{}' \; | awk '{print $1}' 
  • Juga, findperintah akan mengembalikan banyak kecocokan, tidak hanya satu dan masing-masing kecocokan akan digandakan oleh yang kedua find. Ini berarti bahwa pada titik tertentu Anda akan membandingkan file yang sama dengan dirinya sendiri, md5sum akan identik dan Anda akhirnya akan menghapus semua file Anda (saya menjalankan ini pada test dir yang berisi a.jpgdan 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
  • Anda tidak ingin menjalankan for i in directory_pathkecuali Anda melewati array direktori. Jika semua file ini berada di direktori yang sama, Anda ingin menjalankan for i in $(find directory_path -iname "*.jpg") untuk menelusuri semua file.

  • Adalah ide yang buruk untuk menggunakan forloop dengan output dari find. Anda harus menggunakan whileloop atau globbing :

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

    atau, jika semua file Anda berada di direktori yang sama:

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

    Bergantung pada shell Anda dan opsi yang telah Anda tentukan, Anda dapat menggunakan globbing bahkan untuk file dalam subdirektori, tetapi jangan membahasnya di sini.

  • Akhirnya, Anda juga harus mengutip variabel Anda jalur direktori lain dengan spasi akan merusak skrip Anda.

Nama file dapat berisi spasi, baris baru, garis miring terbalik dan karakter aneh lainnya, untuk mengatasinya dengan benar dalam satu whilelingkaran, Anda harus menambahkan beberapa opsi lagi. Yang ingin Anda tulis adalah sesuatu seperti:

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

Cara yang lebih sederhana adalah:

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

Versi yang lebih baik yang dapat menangani spasi dalam nama file:

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

Script Perl kecil ini akan dijalankan melalui hasil dari findperintah (yaitu md5sum dan nama file). The -apilihan untuk perllini perpecahan masukan pada spasi dan menyimpannya dalam Farray, sehingga $F[0]akan menjadi md5sum dan $F[1]nama file. Md5sum disimpan dalam hash kdan skrip memeriksa apakah hash sudah terlihat ( if $k{$F[0]}>1) dan menghapus file jika hash ( system("rm $F[1]")).


Meskipun itu akan berhasil, akan sangat lambat untuk koleksi gambar besar dan Anda tidak dapat memilih file mana yang akan disimpan. Ada banyak program yang menangani ini dengan cara yang lebih elegan termasuk:


+1 untuk cuplikan Perl. Sangat elegan! Anda juga dapat menggunakan milik Perl unlinkalih-alih membuat systempanggilan.
Joseph R.

@ JosephephR. terima kasih :) Memiliki bug meskipun, itu akan gagal untuk nama file dengan spasi karena hanya karakter pertama dari nama hingga ruang pertama akan di $F[1]. Memperbaikinya menggunakan irisan array. Adapun unlink () saya tahu, tetapi ingin menjaga perlism ke minimum dan system call lebih mudah dimengerti jika Anda tidak tahu Perl.
terdon

13

Ada program bagus yang disebut fdupesyang menyederhanakan seluruh proses dan meminta pengguna untuk menghapus duplikat. Saya pikir itu perlu diperiksa:

$ 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

Pada dasarnya, itu meminta saya untuk menyimpan file mana , saya mengetik 1 , dan menghapus yang kedua.

Pilihan menarik lainnya adalah:

-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

Dari contoh Anda, Anda mungkin ingin menjalankannya sebagai:

fdupes --recurse --delete --noprompt DIRECTORY_WITH_DUPLICATES

Lihat man fdupessemua opsi yang tersedia.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.