trova e rimuovi duplicati in una directory


12

Ho una directory con più file img e alcuni sono identici ma hanno tutti nomi diversi. Devo rimuovere i duplicati ma senza strumenti esterni solo con uno bashscript. Sono un principiante in Linux. Ho provato il ciclo nidificato per confrontare le md5somme e, a seconda del risultato, rimuovere ma qualcosa non va nella sintassi e non funziona. qualsiasi aiuto?

quello che ho provato è ...

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

Ottengo: test: too many arguments


Includi anche tutti i messaggi di errore che ricevi nella tua domanda.
terdon

Perché non puoi usare strumenti esterni come fdupes? La risposta di @terdon è sorprendente, ma evidenzia davvero perché usare un buon strumento è la strada da percorrere se possibile. Se si tratta di una sorta di hardware o server dedicato, potresti essere ancora in grado di accedervi tramite una rete, ecc. Da una macchina che ha strumenti come fdupes disponibili.
Joe,

Risposte:


28

Ci sono alcuni problemi nella tua sceneggiatura.

  • In primo luogo, al fine di assegnare il risultato di un comando a una variabile è necessario racchiuderlo sia in backtics ( `command`) o, preferibilmente, $(command). Lo hai tra virgolette singole ( 'command') che invece di assegnare il risultato del tuo comando alla tua variabile, assegna il comando stesso come una stringa. Pertanto, il tuo testè in realtà:

    $ echo "test $sum1=$sum2"
    test find $i -type f -iname "*.jpg" -exec md5sum {} \;=find $j -type f -iname "*.jpg" -exec md5sum {} \;
  • Il prossimo problema è che il comando md5sumrestituisce più dell'hash:

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

    Vuoi solo confrontare il primo campo, quindi dovresti analizzare l' md5sumoutput passandolo attraverso un comando che stampa solo il primo campo:

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

    o

    find $i -type f -iname "*.png" -exec md5sum '{}' \; | awk '{print $1}' 
  • Inoltre, il findcomando restituirà molte corrispondenze, non solo una e ognuna di queste corrispondenze verrà duplicata dalla seconda find. Ciò significa che a un certo punto confronterai lo stesso file con se stesso, il md5sum sarà identico e finirai per eliminare tutti i tuoi file (l'ho eseguito su una directory di test contenente a.jpge 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
  • Non si desidera eseguire for i in directory_pathse non si passa una matrice di directory. Se tutti questi file si trovano nella stessa directory, si desidera eseguire for i in $(find directory_path -iname "*.jpg") per esaminare tutti i file.

  • È una cattiva idea usare i forloop con l'output di find. Dovresti usare whileloop o globbing :

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

    oppure, se tutti i tuoi file si trovano nella stessa directory:

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

    A seconda della shell e delle opzioni che hai impostato, puoi usare il globbing anche per i file nelle sottodirectory, ma non entriamo qui.

  • Infine, dovresti anche citare le tue variabili, altrimenti i percorsi di directory con spazi interromperanno il tuo script.

I nomi dei file possono contenere spazi, nuove linee, barre rovesciate e altri caratteri strani, per gestirli correttamente in un whileciclo dovrai aggiungere altre opzioni. Quello che vuoi scrivere è qualcosa di simile:

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

Un modo ancora più semplice sarebbe:

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

Una versione migliore che può gestire gli spazi nei nomi dei file:

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

Questo piccolo script Perl eseguirà i risultati del findcomando (ovvero md5sum e nome del file). L' -aopzione per perldivide le linee di input negli spazi bianchi e le salva Fnell'array, quindi $F[0]saranno md5sum e $F[1]il nome del file. Md5sum viene salvato nell'hash ke lo script controlla se l'hash è già stato visto ( if $k{$F[0]}>1) ed elimina il file se ha ( system("rm $F[1]")).


Mentre funzionerà, sarà molto lento per raccolte di immagini di grandi dimensioni e non è possibile scegliere quali file conservare. Esistono molti programmi che gestiscono questo in un modo più elegante tra cui:


+1 per lo snippet Perl. Davvero elegante! Puoi anche usare il proprio Perl unlinkinvece di effettuare una systemchiamata.
Joseph R.,

@JosephR. Grazie :). Se avesse un bug, fallirebbe per i nomi di file con spazi poiché sarebbero presenti solo i primi caratteri di un nome fino al primo spazio $F[1]. Risolto il problema usando le sezioni di array. Per quanto riguarda unlink () lo so, ma volevo ridurre al minimo i perlismi e la chiamata di sistema è più facile da capire se non conosci Perl.
terdon

13

Esiste un elegante programma chiamato fdupesche semplifica l'intero processo e richiede all'utente di eliminare i duplicati. Penso che valga la pena verificare:

$ 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

Fondamentalmente, mi ha chiesto quale file conservare , ho digitato 1 e ha rimosso il secondo.

Altre opzioni interessanti sono:

-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

Dal tuo esempio, probabilmente vuoi eseguirlo come:

fdupes --recurse --delete --noprompt DIRECTORY_WITH_DUPLICATES

Vedi man fdupesper tutte le opzioni disponibili.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.