Как удалить дубликаты файлов в каталоге?

25

Я скачал много изображений в каталоге.
Загрузчик переименовал файлы, которые уже существуют.
Я также переименовал некоторые файлы вручную.

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

Как удалить дубликаты? Результат должен быть:

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

примечание: имя не имеет значения. Я просто хочу уникальные файлы.

кэв
источник

Ответы:

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

Это и рекурсивно, и обрабатывает любое имя файла. Недостатком является то, что для использования ассоциативных массивов и рекурсивного поиска требуется версия 4.x. Удалите, echoесли вам нравятся результаты.

версия 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='"' *

Обратите внимание, что это все равно будет работать с файлами, в имени которых есть двойные кавычки. Нет реального способа обойти это с awk. Удалите, echoесли вам нравятся результаты.

SiegeX
источник
хорошо, у меня работала версия bash, но в моем тесте с 2 подобными папками она удалила половину дубликатов в одной папке и половину в другой. Зачем. я ожидал бы удаление всех (дублированных) одной папки.
Ferroao
@Ferroao Возможно, они не были точными дубликатами. Если только один бит отключен от хэша md5, который мой сценарий использует для определения двойственности, он будет совершенно другим. Вы можете добавить echo cksmсразу после строки, начинающейся с, readесли вы хотите видеть хэш каждого файла.
SiegeX
нет, все «дубликаты» (копии) были удалены, осталась 1 версия, скажем, оригинал. половина копий была удалена из одной папки, а другая половина - из другой (100% удаление копий). мои 100% для копий в избытке, а не от всей
суммы
@ Ferroao, я вижу. В этом случае кажется, что когда bash выполняет рекурсивное расширение пути через **, он упорядочивает список таким образом, что две папки перемежаются, а не вся папка 1, а затем вся папка 2. Скрипт всегда оставляет первый «оригинал». он попадает по списку. Вы можете echo $fileперед readстрокой проверить, правда ли это.
SiegeX
45

fdupes - инструмент по вашему выбору. Чтобы найти все дубликаты файлов (по содержанию, а не по имени) в текущем каталоге:

fdupes -r .

Чтобы вручную подтвердить удаление дублированных файлов:

fdupes -r -d .

Чтобы автоматически удалить все копии, кроме первой, каждого дублированного файла ( имейте в виду , это предупреждение, фактически удаляет файлы по запросу ):

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

Я бы рекомендовал вручную проверять файлы перед удалением:

fdupes -rf . | grep -v '^$' > files
... # check files
xargs -a files rm -v
Jakob
источник
Прекрасно работает, но не работает, если имена файлов содержат пробелы.
Даниэль Вольф
1
@DanielWolf попробуйте с опцией xargs-d '\n'
Jakob
1
Кроме того, в более новых версиях fdupes есть встроенная опция для удаления всех, кроме первого, в списке дубликатов файлов: fdupes -rdN .где -r рекурсивно, -d - это удаление и -N - без запроса
Rand
Спасибо, это замечательно, потому что может обнаружить более 2 дубликатов и позволяет выбрать, какой из дубликатов вы хотите сохранить (или все из них).
Smeterlink
1

Будучи немного ленивым, я быстро нашел его в Интернете .

Сначала вы должны создать контрольную сумму CRC для каждого файла, поскольку вы, очевидно, хотите удалить только точные дубликаты.

cksum  *.jpg | sort -n > filelist

Затем переберите этот список файлов, прочитав контрольную сумму, а также имя файла. Если две контрольные суммы совпадают, файл будет удален. Это работает, так как сортировка является числовой и сортирует только по контрольным суммам, которые группируют дубликаты файлов.

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

Очевидно, это не работает рекурсивно.

slhck
источник
1

Как проверить файлы, имеющие уникальный контент?

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

Как мы можем получить список файлов в каталоге?

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

Мы можем получить любые 2 файла из этого списка и проверить, не отличаются ли их имена и содержимое.

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

Например, у нас есть каталог:

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

Таким образом, есть только 3 уникальных файла.

Давайте запустим этот скрипт:

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

И мы получаем только 3 файла.

$> ls .tmp/ -1
all.txt
file
text(2)
ДМИТРИЙ МАЛИКОВ
источник
1

Я написал этот крошечный скрипт для удаления дублированных файлов

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

В основном он использует временный файл ( /tmp/list.txt) для создания карты файлов и их хэшей. Позже я использую эти файлы и магию каналов Unix, чтобы сделать все остальное.

Скрипт ничего не удалит, но напечатает команды для удаления файлов.

mfilter.sh ./dir | bash

Надеюсь, это поможет

crodas
источник
1

Более краткая версия удаления дублированных файлов (всего одна строка)

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

Я нашел более простой способ выполнить ту же задачу

for i in `md5sum * | sort -k1 | uniq -w32 -d|awk '{print $2}'`; do
rm -rf $i
done
Прашант Лахера
источник
0

Большинство и, возможно, все остальные ответы ужасно неэффективны из-за вычисления контрольной суммы каждого файла в каталоге для обработки.

Потенциально более быстрый подход заключается в том, чтобы сначала получить размер каждого файла, который является почти немедленным ( lsили stat), а затем вычислить и сравнить контрольные суммы только для файлов, имеющих не уникальный размер.

jlliagre
источник
0

Это не то, что вы спрашиваете, но я думаю, что кто-то может найти это полезным, когда контрольные суммы не совпадают, но имя схоже (с суффиксом в скобках). Этот скрипт удаляет файлы с суффиксами как («цифра»)

#! /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
источник
-3

Я нашел небольшую программу, которая действительно упрощает такие задачи: fdupes .

Рики Нефф
источник
Пожалуйста, добавьте инструкции по установке и пример использования, соответствующий данному вопросу.
simlev