Como remover arquivos duplicados em um diretório?


25

Eu baixei muitas imagens em um diretório.
O Downloader renomeou os arquivos que já existem.
Também renomei alguns dos arquivos manualmente.

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

Como remover os duplicados? O resultado deve ser:

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

nota: o nome não importa. Eu só quero arquivos uniq.

Respostas:


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

Isso é recursivo e lida com qualquer nome de arquivo. A desvantagem é que ela requer a versão 4.x para a capacidade de usar matrizes associativas e pesquisa recursiva. Remova o echose você gosta dos resultados.

versão 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='"' *

Observe que isso ainda será interrompido em arquivos que tenham aspas duplas em seus nomes. Não há maneira real de contornar isso awk. Remova o echose você gosta dos resultados.


bem, a versão do bash funcionou para mim, mas no meu teste, com duas pastas semelhantes, ele excluiu metade das duplicatas em uma pasta e metade na outra. porque. eu esperaria a exclusão de todos (duplicados) de uma pasta.
Ferroao 5/12

@ Ferrero Talvez não tenham sido duplicatas exatas. Se apenas um bit estiver fora do hash md5 que meu script está usando para determinar a duplicidade, seria completamente diferente. Você pode adicionar um echo cksmlogo após a linha que começa com readse quiser ver o hash de cada arquivo.
SiegeX

não, todas as "duplicatas" (cópias) foram removidas, permanecendo 1 versão, digamos que o original. metade das cópias foram excluídas de uma pasta e a outra metade da outra pasta (exclusão de 100% das cópias). meu 100% é para cópias em excesso, não da totalidade
Ferroao

@Ferroao eu vejo. Nesse caso, parece que quando o bash faz a expansão do caminho recursivo **, ele ordena a lista de tal maneira que as duas pastas sejam intercaladas em vez de todas da pasta 1 e depois da pasta 2. O script sempre deixará o primeiro 'original' ele atinge conforme percorre a lista. Você pode echo $fileantes da readlinha para ver se isso é verdade.
SiegeX

45

O fdupes é a ferramenta de sua escolha. Para encontrar todos os arquivos duplicados (por conteúdo, não por nome) no diretório atual:

fdupes -r .

Para confirmar manualmente a exclusão de arquivos duplicados:

fdupes -r -d .

Para excluir automaticamente todas as cópias, exceto a primeira de cada arquivo duplicado ( esteja avisado, este aviso, na verdade, ele exclui arquivos, conforme solicitado ):

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

Eu recomendo verificar manualmente os arquivos antes da exclusão:

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

Funciona muito bem, mas falha se os nomes dos arquivos contiverem espaços.
Daniel Lobo

1
@DanielWolf tentar opção xargs com-d '\n'
Jakob

1
Além disso, as versões mais recentes do fdupes têm a opção interna de excluir todos, exceto o primeiro de uma lista de arquivos duplicados: fdupes -rdN .onde -r é recursivo, -d é excluir e -N é sem aviso
prévio

Obrigado, isso é excelente porque pode detectar mais de duas duplicatas e permite selecionar qual dos dups você deseja preservar (ou todos).
Smeterlink


1

Sendo um pouco preguiçoso, não demorei muito para encontrar um online .

Primeiro você precisa criar uma soma de verificação CRC de cada arquivo, pois obviamente você deseja remover apenas duplicatas exatas.

cksum  *.jpg | sort -n > filelist

Em seguida, itere sobre essa lista de arquivos, lendo a soma de verificação e também o nome do arquivo. Se duas somas de verificação forem iguais, o arquivo será removido. Isso funciona, pois a classificação é numérica e só classifica nas somas de verificação, que agrupam arquivos duplicados.

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

Obviamente, isso não funciona recursivamente.


1

Como testar arquivos com conteúdo exclusivo?

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

Como podemos obter a lista de arquivos no diretório?

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

Podemos obter dois arquivos dessa lista e verificar se os nomes deles são diferentes e o conteúdo é o mesmo.

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

Por exemplo, temos alguns dir:

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

Portanto, existem apenas 3 arquivos exclusivos.

Vamos executar esse 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 temos apenas 3 arquivos com folhas.

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

1

Eu escrevi este pequeno script para excluir arquivos duplicados

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

Basicamente, ele usa um arquivo temporário ( /tmp/list.txt) para criar um mapa de arquivos e seus hashes. Mais tarde eu uso esses arquivos e a magia dos pipes do Unix para fazer o resto.

O script não excluirá nada, mas imprimirá os comandos para excluir arquivos.

mfilter.sh ./dir | bash

Espero que ajude


1

Versão mais concisa da remoção de arquivos duplicados (apenas uma linha)

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

Encontrei uma maneira mais fácil de executar a mesma tarefa

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

0

A maioria e possivelmente todas as respostas restantes são terrivelmente ineficientes, calculando a soma de verificação de cada arquivo no diretório a ser processado.

Uma abordagem potencialmente mais rápida da ordem de magnitude é obter primeiro o tamanho de cada arquivo, que é quase imediato ( lsou stat), e depois calcular e comparar as somas de verificação apenas para os arquivos com tamanho não exclusivo.


0

Não é isso que você está perguntando, mas acho que alguém pode achar útil quando as somas de verificação não são iguais, mas o nome é semelhante (com sufixo entre parênteses). Este script remove os arquivos com sufixos como ("dígito")

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

Encontrei um pequeno programa que realmente simplifica esse tipo de tarefa: fdupes .


Adicione instruções de instalação e um exemplo de uso apropriado para a pergunta.
simlev 21/09/19
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.