sitio personal de Rodrigo Garcia.

Utilidades python 1

permalink.

Actualizado - 31 August 2019

En este post compartiré un útil script en python para mostrar archivos duplicados, eliminarnos o "mantenerlos" creando un enlace simbólico para no perder el acceso y ahorrar espacio.

Es una mejora del script presentado antes en un post anterior de utilidades bash Utilidades-bash-1.

La idea ahora es que además de comprobar el contenido de los archivos en un directorio y mostrar cúales son duplicados, permitir eliminar los archivos repetidos o alternativamente crear un enlace simbólico en lugar de los archivos que se han eliminado por ser repetidos.

Primero veremos una muestra conceptual de cómo funciona y luego mostraremos el script.

Gráficamente tenemos este directorio:

/tmp/dir
├── d1
│   ├── 1.pdf
│   └── docs
│       ├── 1.pdf
│       └── 90.odt
├── d2
│   ├── 4.odt
│   └── 90.odt
└── d3
    ├── docs
    │   └── 90.odt
    └── ruta.png

Aplicando el script:

python3 archivosDuplicados.py /tmp/dir

Muestra que existen tres archivos duplicados en este caso: 1.pdf se repite una vez y 90.pdf dos veces.

Eliminando duplicados

Cuando se usa con la opción -d elimina los duplicados en este caso quedaría:

/tmp/dir
├── d1
│   ├── 1.pdf
│   └── docs
├── d2
│   ├── 4.odt
│   └── 90.odt
└── d3
    ├── docs
    └── ruta.png

Creando enlaces simbólicos

Cuando se usa con la opción -s elimina los duplicados y crea un enlace simbólico a un archivo con el contenido similar.

/tmp/dir
├── d1
│   ├── 1.pdf
│   └── docs
│       ├── 1.pdf -> /tmp/dir/d1/1.pdf
│       └── 90.odt -> /tmp/dir/d2/90.odt
├── d2
│   ├── 4.odt
│   └── 90.odt
└── d3
    ├── docs
    │   └── 90.odt -> /tmp/dir/d2/90.odt
    └── ruta.png -> /tmp/dir/d2/4.odt

ArchivosDuplicados.py

El script actualizado se encuentra en https://notabug.org/strysg/duplicados.py, aquí una versión funcional:

#!/usr/bin/python3
'''
Buscador de archivos duplicados en un árbol de directorios
'''
import hashlib
import os
import sys

# Funciones de ayuda
def getList(directory="."):
''' retorna una lista con los nombres de todos los archivos dentro el 
directorio actual.
    * basado en https://stackoverflow.com/questions/120656/directory-tree-listing-in-python#120701
    '''
    files = []
    for dirname, dirnames, filenames in os.walk(directory):
        # for subdirname in dirnames:
        #     files.append(os.apth.join(dirname, subdirname))
        for filename in filenames:
            files.append(os.path.join(dirname,filename))
    return files

def digest(filename, algorithm='sha1'):
    ''' returns hexdigest of the given filename using the given algorithm '''
    with open(filename, 'r+b') as fil:
        if (algorithm == 'sha1'):
            return hashlib.sha1(fil.read()).hexdigest()
        elif (algorithm == 'sha224'):
            return hashlib.sha224(fil.read()).hexdigest()
        elif (algorithm == 'sha256'):
            return hashlib.sha256(fil.read()).hexdigest()
        elif (algorithm == 'sha384'):
            return hashlib.sha384(fil.read()).hexdigest()
        elif (algorithm == 'sha512'):
            return hashlib.sha512(fil.read()).hexdigest()
        elif (algorithm == 'md5'):
            return hashlib.md5(fil.read()).hexdigest()
        print ('Invalid algorithm')
        return ""

def digests(fileList, algorithm='sha1'):
    ''' Retorna un diccionario con los digestos calculados de la lista de
    archivos `fileList'.
    '''
    dict = {}
    for file in fileList:
        d = digest(file)
        if d in dict:
            l = dict[d]
            l.append(file)
            dict[d] = l

    for file in fileList:
        d = digest(file)
        if d in dict:
            # duplicado encontrado
            l = dict[d]
            l.append(file)
            dict[d] = l
        else:
            l = []
            l.append(file)
            dict[d] = l
    return dict

def eliminarArchivo(archivo):
    ''' elimina el archivo con ruta absoluta 
    -- return: True si lo logra.
    '''
    if os.path.exists(archivo):
        os.remove(archivo)
        return True
    else:
        print('El archivo no existe:', archivo)
        return False

def crearEnlaceSimbolico(fuente, destino):
    ''' crea un enlace simbolico que hace que `destino' apunte a `fuente' 
    -- return: True si lo logra.
    '''
    if not os.path.exists(fuente):
        print ('El archivo no existe:', fuente)
        return False
    if not os.path.exists(destino):
        print ('El archivo no existe:', destino)
        False
    try:
        os.symlink(fuente, destino)
        print ('creado enlace simbolico')
        return True
    except ex:
        print ('Excepcion generada:', str(ex))
        return False

def eliminarYCrearEnlaceSimbolico(fuente, destino):
    ''' Elimina el archivo `destino' y en el mismo directorio crea un enlace simbolico
    que apunta al archivo `fuente'.
    -- return: True si lo logra.
    '''
    if not eliminarArchivo(destino):
        return False
    if not crearEnlaceSimbolico(fuente, destino):
        return False
    return True

def use():
    print ('Obtiene un lista de archivos duplicados desde un directorio raíz')
    print ('Cada linea contiene los archivos que se ha detectado iguales')
    print ()
    print ('  python3 duplicados.py [opcion] [DIR] [ALGORITMO]')
    print ()
    print (' - DIR: Directorio raíz donde realizar la búsqueda usa "." por defecto')
    print (' - ALGORITMO: Algoritmo para obtener digestos "sha1" por defecto')
    print ('              permitidos; md5,sha1,sha224,sha256,sha384,sha512')
    print (' opcion:')
    print ('   d: Elimina los archivos duplicados dejando solo un archivo fuente.')
    print ('   s: Elimina los archivos duplicados y crea un enlace simbolico hacia el archivo fuente en lugar de los archivos eliminados.')
    print ('EJEMPLO')
    print ('    python3 duplicados.py /tmp/dir1')
    print ('    python3 duplicados.py -d /tmp/dir1')
    print ('    python3 duplicados.py -s /tmp/dir1')

################################ main ####################
files = []
directory='.'
hashAlgorithm = "sha1"

eliminar_duplicados = False
crear_enlaces_simbolicos = False

# Examinando opciones introducidas al llamar al script
if (len(sys.argv) > 1):
    if (sys.argv[1] != ''):
        if (sys.argv[1]=='-h' or sys.argv[1]=='--help'):
            use()
            exit(0)
        if (sys.argv[1]=='-d'):
            eliminar_duplicados = True
            directory=sys.argv[2]
        elif (sys.argv[1]=='-s'):
            crear_enlaces_simbolicos = True
            directory=sys.argv[2]
        else:
            directory=sys.argv[1]

# Comprobando si se especifica algoritmo para obtener digestos
if (len(sys.argv) > 3):
    if (eliminar_duplicados or crear_enlaces_simbolicos and sys.argv[3] != ''):
        if (sys.argv[3]=='sha1' or sys.argv[3]=='md5' or sys.argv[3]=='sha224'
            or sys.argv[3]=='sha256' or sys.argv[3]=='sha512'):
            hashAlgorithm = sys.argv[3]
        else:
            use()
            exit(0)

############## Programa principal
files = getList(directory)
digests = digests(files, hashAlgorithm)
duplicados = 0
completados = []
erroneos = []

# Examinando la lista de digestos y archivos
for digest, lista in digests.items():
    if len(lista) > 1:
        # significa que hay archivos duplicados para este digesto calculado
        listaDuplicados = []
        duplicados += 1
        fuente = lista[0]
        c = 0
        for file in lista:
            listaDuplicados.append(file)
            if eliminar_duplicados and c > 0:
                if eliminarArchivo(file):
                    completados.append(file)
                else:
                    erroneos.append(file)
            if crear_enlaces_simbolicos and c > 0:
                if eliminarYCrearEnlaceSimbolico(fuente, file):
                    completados.append(file)
                else:
                    erroneos.append(file)
            c += 1
        # mostrando
        for file in listaDuplicados:
            print (file)
        print (len(listaDuplicados))
        print ()

    print ('')
    print ('# completados satisfactoriamente #')
    for file in completados:
        print (file)
    print ('')
    print ('# errores generados #') 
    for file in erroneos:
        print (file)
    print ('----')
    print (' - Total Duplicados:', str(duplicados))
    print (' - Completados:', str(len(completados)))
    print (' - Erroneos:', str(len(erroneos)))
    exit(0)

La utilidad de este script se puede aplicar a directorios grandes ya que examina los subdirectorios también. Sin embargo hay que considerar que como por cada archivo y para comprobar que está o no repetido, se comprueba su contenido ignorando su nombre. La velocidad del cálculo del digesto depende del algoritmo que se use por defecto se usa el algoritmo sha1.

Posts/Noticias relacionadas

Como publicar un paquete python al repositorio pypi


Utilidades bash 1


Charla jugando con redireccion en bash


Accediendo a equipos de casa mediante vpn wireguard


Construir firmware para enrutadores con buildroot


Probando mbedtls


Probando bmx7


Libro Programming in C


Consejo del día

Trata de comprar artículos de tiendas o puestos de mercado familiares, esto por que cuando lo haces apoyas directamente a familias o personas individuales que luchan por subsistir.

En cambio cuando compras de supermercados o cadenas de tiendas grandes, estás dando ganacias adicionales a empresarios o empresarias grandes principalmente.