Come rimuovere i file duplicati in una directory?


25

Ho scaricato molte immagini in una directory.
Downloader ha rinominato i file già esistenti.
Ho anche rinominato manualmente alcuni file.

a.jpg
b.jpg
b(2).jpg
hello.jpg      <-- manually renamed `b(3).jpg`
c.jpg
c(2).jpg
world.jpg      <-- manually renamed `d.jpg`
d(2).jpg
d(3).jpg

Come rimuovere quelli duplicati? Il risultato dovrebbe essere:

a.jpg
b.jpg
c.jpg
world.jpg

nota: il nome non ha importanza. Voglio solo file uniq.

Risposte:


27

bash 4.x

#!/bin/bash
declare -A arr
shopt -s globstar

for file in **; do
  [[ -f "$file" ]] || continue

  read cksm _ < <(md5sum "$file")
  if ((arr[$cksm]++)); then 
    echo "rm $file"
  fi
done

Questo è sia ricorsivo che gestisce qualsiasi nome di file. L'aspetto negativo è che richiede la versione 4.x per la capacità di utilizzare array associativi e ricerche ricorsive. Rimuovi echose ti piacciono i risultati.

versione gawk

gawk '
  {
    cmd="md5sum " q FILENAME q
    cmd | getline cksm
    close(cmd)
    sub(/ .*$/,"",cksm)
    if(a[cksm]++){
      cmd="echo rm " q FILENAME q
      system(cmd)
      close(cmd)
    }
    nextfile
  }' q='"' *

Si noti che questo si interromperà ancora sui file che hanno le virgolette doppie nel loro nome. Non c'è modo reale di aggirare questo awk. Rimuovi echose ti piacciono i risultati.


bene, la versione bash ha funzionato per me, ma nel mio test, con 2 cartelle simili, ha eliminato metà dei duplicati in una cartella e metà nell'altra. perché. mi aspetterei la cancellazione di tutti (duplicati) di una cartella.
Ferroao,

@Ferroao Forse non erano duplicati esatti. Se solo un bit è fuori dall'hash md5 che il mio script sta usando per determinare la duplicità sarebbe completamente diverso. Se vuoi vedere l'hash di ogni file, puoi aggiungere echo cksmsubito dopo la riga read.
SiegeX,

no, tutti i "duplicati" (copie) sono stati rimossi, rimanendo 1 versione, diciamo l'originale. metà copie sono state eliminate da una cartella e l'altra metà dall'altra cartella (eliminazione del 100% delle copie). il mio 100% è per le copie in eccesso, non della totalità
Ferroao il

@Ferroao vedo. In tal caso, quando bash esegue l'espansione del percorso ricorsivo tramite **, ordina l'elenco in modo tale che le due cartelle siano interlacciate anziché tutta la cartella 1, quindi tutta la cartella 2. Lo script lascerà sempre il primo "originale" colpisce mentre scorre l'elenco. Puoi echo $fileprima della readriga per vedere se questo è vero.
SiegeX il

45

fdupes è lo strumento che preferisci . Per trovare tutti i file duplicati (per contenuto, non per nome) nella directory corrente:

fdupes -r .

Per confermare manualmente l'eliminazione di file duplicati:

fdupes -r -d .

Per eliminare automaticamente tutte le copie tranne il primo di ogni file duplicato ( attenzione, questo avviso, in realtà elimina i file, come richiesto ):

fdupes -r -f . | grep -v '^$' | xargs rm -v

Consiglio di controllare manualmente i file prima dell'eliminazione:

fdupes -rf . | grep -v '^$' > files
... # check files
xargs -a files rm -v

Funziona alla grande, ma fallisce se i nomi dei file contengono spazi.
Daniel Wolf,

1
@DanielWolf prova con l'opzione xargs-d '\n'
Jakob,

1
Inoltre, le versioni più recenti di fdupes hanno l'opzione integrata per eliminare tutto tranne il primo in un elenco di file duplicati: fdupes -rdN .dove -r è ricorsivo, -d è eliminato e -N non è richiesto
Rand

Grazie, questo è eccezionale perché può rilevare più di 2 duplicati e ti consente di selezionare quale dei duplicati vuoi preservare (o tutti).
Smeterlink


1

Essendo un po 'pigro, non mi ci è voluto molto per trovarne uno online .

Devi prima creare un checksum CRC per ogni file, poiché ovviamente desideri rimuovere solo i duplicati esatti.

cksum  *.jpg | sort -n > filelist

Quindi, scorrere su questo elenco di file, leggendo il checksum e anche il nome file. Se due checksum sono uguali, il file verrà rimosso. Funziona, poiché l'ordinamento è numerico e ordina solo sui checksum, che raggruppa i file duplicati.

old=""
while read sum lines filename
do
      if [[ "$sum" != "$old" ]] ; then
            old="$sum"
            continue
      fi
      rm -f "$filename"
done < filelist

Ovviamente, questo non funziona in modo ricorsivo.


1

Come testare file con contenuti unici?

if diff "$file1" "$file2" > /dev/null; then
    ...

Come possiamo ottenere un elenco di file nella directory?

files="$( find ${files_dir} -type f )"

Possiamo ottenere 2 file dall'elenco e verificare se i loro nomi sono diversi e il contenuto è lo stesso.

#!/bin/bash
# removeDuplicates.sh

files_dir=$1
if [[ -z "$files_dir" ]]; then
    echo "Error: files dir is undefined"
fi

files="$( find ${files_dir} -type f )"
for file1 in $files; do
    for file2 in $files; do
        # echo "checking $file1 and $file2"
        if [[ "$file1" != "$file2" && -e "$file1" && -e "$file2" ]]; then
            if diff "$file1" "$file2" > /dev/null; then
                echo "$file1 and $file2 are duplicates"
                rm -v "$file2"
            fi
        fi
    done
done

Ad esempio, abbiamo alcune dir:

$> ls .tmp -1
all(2).txt
all.txt
file
text
text(2)

Quindi ci sono solo 3 file univoci.

Eseguiamo quello script:

$> ./removeDuplicates.sh .tmp/
.tmp/text(2) and .tmp/text are duplicates
removed `.tmp/text'
.tmp/all.txt and .tmp/all(2).txt are duplicates
removed `.tmp/all(2).txt'

E abbiamo lasciato solo 3 file.

$> ls .tmp/ -1
all.txt
file
text(2)

1

Ho scritto questo piccolo script per eliminare i file duplicati

https://gist.github.com/crodas/d16a16c2474602ad725b

Fondamentalmente utilizza un file temporaneo ( /tmp/list.txt) per creare una mappa di file e dei loro hash. Più tardi uso quei file e la magia delle pipe Unix per fare il resto.

Lo script non eliminerà nulla ma stamperà i comandi per eliminare i file.

mfilter.sh ./dir | bash

Spero che sia d'aiuto


1

Versione più concisa di rimozione di file duplicati (solo una riga)

young@ubuntu-16:~/test$ md5sum `find ./ -type f` | sort -k1 | uniq -w32 -d | xargs rm -fv

find_same_size.sh

#!/usr/bin/env bash
#set -x
#This is small script can find same size of files.
find_same_size(){

if [[ -z $1 || ! -d $1 ]]
then
echo "Usage $0 directory_name" ;
 exit $?
else
dir_name=$1;
echo "current directory is $1"



for i in $(find $dir_name -type f); do
   ls -fl $i
done | awk '{f=""
        if(NF>9)for(i=9;i<=NF;i++)f=f?f" "$i:$i; else f=$9;
        if(a[$5]){ a[$5]=a[$5]"\n"f; b[$5]++;} else a[$5]=f} END{for(x     in b)print a[x] }' | xargs stat -c "%s  %n" #For just list files
 fi
   }

find_same_size $1


young@ubuntu-16:~/test$ bash find_same_size.sh tttt/ | awk '{ if($1 !~   /^([[:alpha:]])+/) print $2}' | xargs md5sum | uniq -w32 -d | xargs rm -vf

0

Ho trovato un modo più semplice per eseguire la stessa attività

for i in `md5sum * | sort -k1 | uniq -w32 -d|awk '{print $2}'`; do
rm -rf $i
done

0

La maggior parte e forse tutte le risposte rimanenti sono terribilmente inefficienti calcolando il checksum di ogni singolo file nella directory da elaborare.

Un approccio potenzialmente più rapido di ordini di grandezza è innanzitutto ottenere la dimensione di ciascun file, che è quasi immediata ( lso stat), quindi calcolare e confrontare i checksum solo per i file con dimensioni non univoche.


0

Non è quello che stai chiedendo, ma penso che qualcuno potrebbe trovarlo utile quando i checksum non sono gli stessi, ma il nome è simile (con il suffisso tra parentesi). Questo script rimuove i file con suffissi come ("digit")

#! /bin/bash
# Warning: globstar excludes hidden directories.
# Turn on recursive globbing (in this script) or exit if the option is not supported:
shopt -s globstar || exit
for f in **
do
extension="${f##*.}"
#get only files with parentheses suffix
FILEWITHPAR=$( echo "${f%.*}".$extension | grep -o -P "(.*\([0-9]\)\..*)")
# print file to be possibly deleted
if [ -z "$FILEWITHPAR" ] ;then
:
else
echo "$FILEWITHPAR ident"
# identify if a similar file without suffix exists
FILENOPAR=$(echo $FILEWITHPAR | sed -e 's/^\(.*\)([0-9])\(.*\).*/\1\2/')
echo "$FILENOPAR exists?"
if [ -f "$FILENOPAR" ]; then
#delete file with suffix in parentheses
echo ""$FILEWITHPAR" to be deleted"
rm -Rf "$FILEWITHPAR"
else
echo "no"
fi
fi
done

-3

Ho trovato un piccolo programma che semplifica davvero questo tipo di attività: fdupes .


Aggiungi le istruzioni di installazione e un esempio di utilizzo appropriato per la domanda.
simlev,
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.