Jak usunąć zduplikowane pliki z katalogu?

25

Pobrałem wiele zdjęć z katalogu.
Downloader zmienił nazwy plików, które już istnieją.
Zmieniłem też nazwę niektórych plików ręcznie.

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

Jak usunąć zduplikowane? Wynik powinien być:

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

Uwaga: nazwa nie ma znaczenia. Chcę tylko pliki uniq.

kev
źródło

Odpowiedzi:

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

Jest to zarówno rekurencyjne, jak i obsługuje dowolną nazwę pliku. Minusem jest to, że wymaga wersji 4.x, aby móc korzystać z tablic asocjacyjnych i wyszukiwania rekurencyjnego. Usuń, echojeśli lubisz wyniki.

wersja 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='"' *

Pamiętaj, że nadal będzie to działać na pliki, które mają podwójne cudzysłowy w nazwie. Nie ma prawdziwego sposobu na obejście tego awk. Usuń, echojeśli lubisz wyniki.

SiegeX
źródło
dobrze, wersja bash działała dla mnie, ale w moim teście, z 2 podobnymi folderami, usunęłam połowę duplikatów w jednym folderze, a połowę w drugim. czemu. oczekiwałbym usunięcia wszystkich (zduplikowanych) z jednego folderu.
Ferroao,
@Ferroao Być może nie były to dokładne duplikaty. Jeśli tylko jeden bit jest wyłączony, skrót md5, którego mój skrypt używa do określenia duplikatu, byłby zupełnie inny. Możesz dodać echo cksmtuż po linii, zaczynając od, readjeśli chcesz zobaczyć skrót każdego pliku.
SiegeX,
nie, wszystkie „duplikaty” (kopie) zostały usunięte, pozostała 1 wersja, powiedzmy oryginał. połowa kopii została usunięta z jednego folderu, a druga połowa z drugiego folderu (100% usunięcia kopii). moje 100% dotyczy kopii w nadmiarze, a nie całości
Ferroao,
@Ferroao Rozumiem. W takim przypadku wydaje się, że gdy bash rozszerzy swoją rekursywną ścieżkę **, porządkuje listę w taki sposób, że dwa foldery są przeplatane, a nie cały folder 1, a następnie cały folder 2. Skrypt zawsze pozostawi pierwszy „oryginalny” uderza podczas iteracji po liście. Możesz echo $fileprzed readwierszem sprawdzić, czy to prawda.
SiegeX,
45

fdupes to wybrane narzędzie. Aby znaleźć wszystkie zduplikowane pliki (według zawartości, a nie nazwy) w bieżącym katalogu:

fdupes -r .

Aby ręcznie potwierdzić usunięcie zduplikowanych plików:

fdupes -r -d .

Aby automatycznie usunąć wszystkie kopie oprócz pierwszego każdego zduplikowanego pliku ( uwaga, to ostrzeżenie, tak naprawdę usuwa pliki, zgodnie z żądaniem ):

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

Zalecam ręczne sprawdzenie plików przed usunięciem:

fdupes -rf . | grep -v '^$' > files
... # check files
xargs -a files rm -v
Jakob
źródło
Działa świetnie, ale kończy się niepowodzeniem, jeśli nazwy plików zawierają spacje.
Daniel Wolf
1
@DanielWolf spróbuj z opcją xargs-d '\n'
Jakob
1
Ponadto nowsze wersje fdupes mają wbudowaną opcję usuwania wszystkich oprócz pierwszej z listy zduplikowanych plików: fdupes -rdN .gdzie -r jest rekurencyjne, -d jest usuwane, a -N nie jest wyświetlane
Rand
Dziękujemy, jest to wyjątkowe, ponieważ może wykryć więcej niż 2 duplikaty i pozwala wybrać, który z duplikatów chcesz zachować (lub wszystkie).
Smeterlink
1

Będąc trochę leniwym, nie zajęło mi długo znalezienie jednego online .

Najpierw musisz utworzyć sumę kontrolną CRC dla każdego pliku, ponieważ oczywiście chcesz jedynie usunąć dokładne duplikaty.

cksum  *.jpg | sort -n > filelist

Następnie iteruj tę listę plików, wczytując sumę kontrolną, a także nazwę pliku. Jeśli dwie sumy kontrolne są takie same, plik zostanie usunięty. Działa to, ponieważ sortowanie jest numeryczne i sortuje tylko sumy kontrolne, które grupują duplikaty plików.

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

Oczywiście nie działa to rekurencyjnie.

slhck
źródło
1

Jak testować pliki o unikalnej zawartości?

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

Jak uzyskać listę plików w katalogu?

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

Możemy pobrać dowolne 2 pliki z tej listy i sprawdzić, czy ich nazwy są różne i czy treść jest taka sama.

#!/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

Na przykład mamy trochę reż:

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

Są więc tylko 3 unikalne pliki.

Uruchommy ten skrypt:

$> ./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'

I pozostały nam tylko 3 pliki.

$> ls .tmp/ -1
all.txt
file
text(2)
ДМИТРИЙ МАЛИКОВ
źródło
1

Napisałem ten mały skrypt, aby usunąć zduplikowane pliki

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

Zasadniczo używa pliku tymczasowego ( /tmp/list.txt) do utworzenia mapy plików i ich skrótów. Później używam tych plików i magii potoków uniksowych do reszty.

Skrypt niczego nie usunie, ale wydrukuje polecenia usuwania plików.

mfilter.sh ./dir | bash

Mam nadzieję, że to pomoże

crodas
źródło
1

Bardziej zwięzła wersja usuwania zduplikowanych plików (tylko jedna linia)

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
niceguy oh
źródło
0

Znalazłem łatwiejszy sposób na wykonanie tego samego zadania

for i in `md5sum * | sort -k1 | uniq -w32 -d|awk '{print $2}'`; do
rm -rf $i
done
Prashant Lakhera
źródło
0

Większość i być może wszystkie pozostałe odpowiedzi są strasznie nieefektywne poprzez obliczenie sumy kontrolnej każdego pliku w katalogu do przetworzenia.

Potencjalnie szybszym podejściem o wielkości rzędu jest najpierw uzyskanie rozmiaru każdego pliku, co jest prawie natychmiastowe ( lslub stat), a następnie obliczenie i porównanie sum kontrolnych tylko dla plików o nie unikalnym rozmiarze.

jlliagre
źródło
0

Nie o to pytasz, ale myślę, że ktoś może się przydać, gdy sumy kontrolne nie są takie same, ale nazwa jest podobna (z przyrostkiem w nawiasach). Ten skrypt usuwa pliki z przyrostkami jako („cyfra”)

#! /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
Ferroao
źródło
-3

Znalazłem mały program, który naprawdę upraszcza tego rodzaju zadania: fdupes .

Ricky Neff
źródło
Dodaj instrukcję instalacji i przykład użycia odpowiedni dla pytania.
simlev