Pruebas

Pruebas

Quiero encontrar archivos duplicados, dentro de un directorio, y luego eliminarlos todos menos uno para recuperar espacio. ¿Cómo logro esto usando un script de shell?

Por ejemplo:

pwd
folder

Los archivos que contiene son:

log.bkp
log
extract.bkp
extract

Necesito comparar log.bkp con todos los demás archivos y, si se encuentra un archivo duplicado (por su contenido), debo eliminarlo. De manera similar, el archivo 'log' debe verificarse con todos los demás archivos siguientes, y así sucesivamente.

Hasta ahora he escrito esto, pero no está dando el resultado deseado.

#!/usr/bin/env ksh
count=`ls -ltrh /folder | grep '^-'|wc -l`
for i in `/folder/*`
do
   for (( j=i+1; j<=count; j++ ))
   do
      echo "Current two files are $i and $j"
      sdiff -s $i  $j
      if [ `echo $?` -eq  0 ]
      then
         echo "Contents of $i and $j are same"
       fi
    done
 done

Respuesta1

Si está contento de usar simplemente una herramienta de línea de comandos y no tener que crear un script de shell, el fdupesprograma está disponible en la mayoría de las distribuciones para hacerlo.

También existe la fslintherramienta basada en GUI que tiene la misma funcionalidad.

Respuesta2

Esta solución encontrará duplicados en tiempo O (n). Cada archivo tiene una suma de verificación generada y cada archivo, a su vez, se compara con el conjunto de sumas de verificación conocidas a través de una matriz asociativa.

#!/bin/bash
#
# Usage:  ./delete-duplicates.sh  [<files...>]
#
declare -A filecksums

# No args, use files in current directory
test 0 -eq $# && set -- *

for file in "$@"
do
    # Files only (also no symlinks)
    [[ -f "$file" ]] && [[ ! -h "$file" ]] || continue

    # Generate the checksum
    cksum=$(cksum <"$file" | tr ' ' _)

    # Have we already got this one?
    if [[ -n "${filecksums[$cksum]}" ]] && [[ "${filecksums[$cksum]}" != "$file" ]]
    then
        echo "Found '$file' is a duplicate of '${filecksums[$cksum]}'" >&2
        echo rm -f "$file"
    else
        filecksums[$cksum]="$file"
    fi
done

Si no especifica ningún archivo (o comodín) en la línea de comando, utilizará el conjunto de archivos en el directorio actual. Comparará archivos en varios directorios, pero no está escrito para que recurra a los directorios mismos.

El "primer" archivo del conjunto siempre se considera la versión definitiva. No se tienen en cuenta los tiempos, permisos o propiedades de los archivos. Sólo se considera el contenido.

Elimine el echode la rm -f "$file"línea cuando esté seguro de que hace lo que desea. Tenga en cuenta que si reemplazara esa línea con ln -f "${filecksums[$cksum]}" "$file"podría vincular el contenido. El mismo ahorro de espacio en disco pero no perderá los nombres de los archivos.

Respuesta3

El problema principal en su secuencia de comandos parece ser que itoma los nombres de archivos reales como valores, mientras que json solo un número. Llevar los nombres a una matriz y usar ambos iy jcomo índices debería funcionar:

files=(*)
count=${#files[@]}
for (( i=0 ; i < count ;i++ )); do 
    for (( j=i+1 ; j < count ; j++ )); do
        if diff -q "${files[i]}" "${files[j]}"  >/dev/null ; then
            echo "${files[i]} and ${files[j]} are the same"
        fi
    done
done

(Parece funcionar con Bash y ksh/ ksh93Debian).

La asignación a=(this that)inicializaría la matriz acon los dos elementos thisy that(con índices 0 y 1). La división de palabras y el globbing funcionan como de costumbre, por lo que files=(*)se inicializan filescon los nombres de todos los archivos en el directorio actual (excepto los archivos de puntos). "${files[@]}"se expandiría a todos los elementos de la matriz, y el signo de almohadilla solicita una longitud, al igual que ${#files[@]}el número de elementos de la matriz. (Tenga en cuenta que ${files}sería el primer elemento de la matriz, y ${#files}es la longitud del primer elemento, ¡no la matriz!)

for i in `/folder/*`

¿Las comillas invertidas aquí seguramente son un error tipográfico? Estaría ejecutando el primer archivo como un comando y dándole el resto como argumentos.

Respuesta4

Por cierto, usar suma de comprobación o hash es una buena idea. Mi guión no lo usa. Pero si los archivos son pequeños y la cantidad de archivos no es grande (como entre 10 y 20 archivos), este script funcionará bastante rápido. Si tiene 100 archivos o más, 1000 líneas en cada archivo, el tiempo será de más de 10 segundos.

Uso: ./duplicate_removing.sh files/*

#!/bin/bash

for target_file in "$@"; do
    shift
    for candidate_file in "$@"; do
        compare=$(diff -q "$target_file" "$candidate_file")
        if [ -z "$compare" ]; then
            echo the "$target_file" is a copy "$candidate_file"
            echo rm -v "$candidate_file"
        fi
    done
done

Pruebas

Crea archivos aleatorios: ./creating_random_files.sh

#!/bin/bash

file_amount=10
files_dir="files"

mkdir -p "$files_dir"

while ((file_amount)); do
    content=$(shuf -i 1-1000)
    echo "$RANDOM" "$content" | tee "${files_dir}/${file_amount}".txt{,.copied} > /dev/null
    ((file_amount--))
done

Correr ./duplicate_removing.sh files/* y obtener salida

the files/10.txt is a copy files/10.txt.copied
rm -v files/10.txt.copied
the files/1.txt is a copy files/1.txt.copied
rm -v files/1.txt.copied
the files/2.txt is a copy files/2.txt.copied
rm -v files/2.txt.copied
the files/3.txt is a copy files/3.txt.copied
rm -v files/3.txt.copied
the files/4.txt is a copy files/4.txt.copied
rm -v files/4.txt.copied
the files/5.txt is a copy files/5.txt.copied
rm -v files/5.txt.copied
the files/6.txt is a copy files/6.txt.copied
rm -v files/6.txt.copied
the files/7.txt is a copy files/7.txt.copied
rm -v files/7.txt.copied
the files/8.txt is a copy files/8.txt.copied
rm -v files/8.txt.copied
the files/9.txt is a copy files/9.txt.copied
rm -v files/9.txt.copied

información relacionada