Utilidades python 1
Conjunto de utilidades python de pruebas 1
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
.