Mi problema es encontrar el prefijo de ruta común de un conjunto dado de archivos.
Literalmente esperaba que “os.path.commonprefix” hiciera exactamente eso. Desafortunadamente, el hecho de que el commonprefix
esté ubicado en la path
es bastante engañoso, ya que en realidad buscará los prefijos de cadena.
La pregunta para mí es, ¿cómo se puede resolver esto realmente por caminos? El problema se mencionó brevemente en esta respuesta (de calificación bastante alta) pero solo como una nota al margen y la solución propuesta (agregando barras a la entrada de prefijo común) Imho tiene problemas, ya que fallará por ejemplo para:
os.path.commonprefix(['/usr/var1/log/', '/usr/var2/log/']) # returns /usr/var but it should be /usr
Para evitar que otros caigan en la misma trampa, podría valer la pena discutir este problema en otra pregunta: ¿Existe una solución simple / portátil para este problema que no se base en verificaciones desagradables del sistema de archivos (es decir, acceda al resultado)? de commonprefix y compruebe si es un directorio y si no devuelve un os.path.dirname
del resultado)?
Hace un tiempo me encontré con esto donde os.path.commonprefix
es un prefijo de cadena y no un prefijo de ruta como se esperaría. Así que escribí lo siguiente:
def commonprefix(l): # this unlike the os.path.commonprefix version # always returns path prefixes as it compares # path component wise cp = [] ls = [p.split('/') for p in l] ml = min( len(p) for p in ls ) for i in range(ml): s = set( p[i] for p in ls ) if len(s) != 1: break cp.append(s.pop()) return '/'.join(cp)
podría hacerse más portátil reemplazando '/'
con os.path.sep
.
Parece que este problema se ha corregido en las versiones recientes de Python. Nueva en la versión 3.5 es la función os.path.commonpath()
, que devuelve la ruta común en lugar del prefijo de cadena común.
Suponiendo que desea la ruta de directorio común, una forma es:
os.path.dirname(filename)
para obtener su ruta de directorio. os.path.abspath( )
para obtener la ruta relativa a la raíz. (Es posible que también desee utilizar os.path.realpath( )
para eliminar enlaces simbólicos). os.path.sep
o os.sep
) al final de todas las rutas de directorio normalizadas. os.path.dirname( )
en el resultado de os.path.commonprefix( )
. En código (sin eliminar enlaces simbólicos):
def common_path(directories): norm_paths = [os.path.abspath(p) + os.path.sep for p in directories] return os.path.dirname(os.path.commonprefix(norm_paths)) def common_path_of_filenames(filenames): return common_path([os.path.dirname(f) for f in filenames])
Un enfoque sólido es dividir la ruta en componentes individuales y luego encontrar el prefijo común más largo de las listas de componentes.
Aquí hay una implementación que es multiplataforma y se puede generalizar fácilmente a más de dos caminos:
import os.path import itertools def components(path): ''' Returns the individual components of the given file path string (for the local operating system). The returned components, when joined with os.path.join(), point to the same location as the original path. ''' components = [] # The loop guarantees that the returned components can be # os.path.joined with the path separator and point to the same # location: while True: (new_path, tail) = os.path.split(path) # Works on any platform components.append(tail) if new_path == path: # Root (including drive, on Windows) reached break path = new_path components.append(new_path) components.reverse() # First component first return components def longest_prefix(iter0, iter1): ''' Returns the longest common prefix of the given two iterables. ''' longest_prefix = [] for (elmt0, elmt1) in itertools.izip(iter0, iter1): if elmt0 != elmt1: break longest_prefix.append(elmt0) return longest_prefix def common_prefix_path(path0, path1): return os.path.join(*longest_prefix(components(path0), components(path1))) # For Unix: assert common_prefix_path('/', '/usr') == '/' assert common_prefix_path('/usr/var1/log/', '/usr/var2/log/') == '/usr' assert common_prefix_path('/usr/var/log1/', '/usr/var/log2/') == '/usr/var' assert common_prefix_path('/usr/var/log', '/usr/var/log2') == '/usr/var' assert common_prefix_path('/usr/var/log', '/usr/var/log') == '/usr/var/log' # Only for Windows: # assert common_prefix_path(r'C:\Programs\Me', r'C:\Programs') == r'C:\Programs'
Hice una pequeña commonpath
común de commonpath
para encontrar rutas comunes de una lista. Viene con algunas opciones agradables.