sitio personal de Rodrigo Garcia Saenz.

Utilidades python 1

permalink.

Conjunto de utilidades python de pruebas 1

Actualizado - 23 July 2023

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.

Actualizaci贸n: Este script es un ejercicio, una utilidad m谩s eficiente y recomendada para buscar y eliminar archivos duplicados en el sistema es fdupes.

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.

Consejo del d铆a

Cuando no est茅s usando el WiFi por ejemplo para dormir ap谩galo. Hay estudios que sugieren que el WiFi provoca dificultad para dormir y podr铆a causar estr茅s card铆aco entre otros (m谩s informaci贸n).