¿Cómo eliminar archivos duplicados en un directorio?


25

Descargué muchas imágenes en un directorio.
Descargador renombrado archivos que ya existen.
También cambié el nombre de algunos de los archivos 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

¿Cómo eliminar los duplicados? El resultado debería ser:

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

nota: el nombre no importa. Solo quiero archivos uniq.

Respuestas:


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

Esto es recursivo y maneja cualquier nombre de archivo. Lo malo es que requiere la versión 4.x para poder usar matrices asociativas y búsquedas recursivas. Elimina el echosi te gustan los resultados.

versión 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='"' *

Tenga en cuenta que esto aún se romperá en los archivos que tienen comillas dobles en su nombre. No hay forma real de evitar eso awk. Elimina el echosi te gustan los resultados.


bien, la versión bash funcionó para mí, pero en mi prueba, con 2 carpetas similares, eliminó la mitad de los duplicados en una carpeta y la otra mitad en la otra. por qué. esperaría la eliminación de todos (duplicados) de una carpeta.
Ferroao

@Ferroao Quizás no fueron duplicados exactos. Si solo un bit está fuera del hash md5 que mi script está usando para determinar la duplicidad, sería completamente diferente. Puede agregar un echo cksmjusto después de la línea que comienza readsi desea ver el hash de cada archivo.
SiegeX

no, se eliminaron todos los "duplicados" (copias), quedando 1 versión, digamos el original. la mitad de las copias se eliminaron de una carpeta y la otra mitad de la otra carpeta (eliminación del 100% de las copias). mi 100% es para copias en exceso, no de la totalidad
Ferroao

@Ferroao ya veo. En ese caso, parece que cuando bash hace su expansión de ruta recursiva **, ordena la lista de tal manera que las dos carpetas se intercalan en lugar de toda la carpeta 1 y luego toda la carpeta 2. El script siempre dejará el primer 'original' golpea mientras itera por la lista. Puede echo $fileantes de la readlínea para ver si esto es cierto.
SiegeX

45

fdupes es la herramienta de su elección. Para buscar todos los archivos duplicados (por contenido, no por nombre) en el directorio actual:

fdupes -r .

Para confirmar manualmente la eliminación de archivos duplicados:

fdupes -r -d .

Para eliminar automáticamente todas las copias, excepto la primera de cada archivo duplicado ( tenga cuidado, esta advertencia, esto realmente elimina los archivos, según lo solicitado ):

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

Recomiendo verificar manualmente los archivos antes de eliminarlos:

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

Funciona muy bien, pero falla si los nombres de archivo contienen espacios.
Daniel Wolf

1
@DanielWolf prueba con la opción xargs-d '\n'
Jakob

1
Además, las versiones más recientes de fdupes tienen la opción incorporada para eliminar todos los archivos duplicados, excepto el primero: fdupes -rdN .donde -r es recursivo, -d es eliminar y -N no es rápido
Rand

Gracias, esto es excepcional porque puede detectar más de 2 duplicados y le permite seleccionar cuál de los duplicados que desea conservar (o todos).
Smeterlink


1

Siendo un poco flojo, no me llevó mucho tiempo encontrar uno en línea .

Primero debe crear una suma de verificación CRC de cada archivo, ya que obviamente solo desea eliminar los duplicados exactos.

cksum  *.jpg | sort -n > filelist

Luego, repita esta lista de archivos, leyendo la suma de comprobación y también el nombre del archivo. Si dos sumas de verificación son iguales, el archivo se eliminará. Esto funciona, ya que el orden es numérico y solo se ordena en las sumas de verificación, que agrupa los archivos duplicados.

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

Obviamente, esto no funciona de forma recursiva.


1

¿Cómo probar archivos con contenido único?

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

¿Cómo podemos obtener la lista de archivos en el directorio?

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

Podemos obtener 2 archivos de esa lista y verificar si sus nombres son diferentes y el contenido es el mismo.

#!/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 ejemplo, tenemos algunos dir:

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

Entonces solo hay 3 archivos únicos.

Vamos a ejecutar ese 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'

Y solo tenemos 3 archivos con hojas.

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

1

Escribí este pequeño script para eliminar archivos duplicados

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

Básicamente utiliza un archivo temporal ( /tmp/list.txt) para crear un mapa de archivos y sus hashes. Más tarde uso esos archivos y la magia de las tuberías Unix para hacer el resto.

El script no eliminará nada, pero imprimirá los comandos para eliminar archivos.

mfilter.sh ./dir | bash

Espero eso ayude


1

Versión más concisa de eliminar archivos duplicados (solo una línea)

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

Encontré una manera más fácil de realizar la misma tarea

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

0

La mayoría y posiblemente todas las respuestas restantes son terriblemente ineficientes al calcular la suma de verificación de todos y cada uno de los archivos en el directorio para procesar.

Un enfoque potencialmente más rápido de órdenes de magnitud es obtener primero el tamaño de cada archivo, que es casi inmediato ( lso stat), y luego calcular y comparar las sumas de verificación solo para los archivos que tienen un tamaño no único.


0

Esto no es lo que está preguntando, pero creo que alguien podría encontrarlo útil cuando las sumas de verificación no son las mismas, pero el nombre es similar (con sufijo entre paréntesis). Este script elimina los archivos con sufijos 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

Encontré un pequeño programa que realmente simplifica este tipo de tareas: fdupes .


Agregue instrucciones de instalación y un ejemplo de uso apropiado para la pregunta.
simlev
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.