Cómo dividir una ruta de dos en sus componentes en Python


153

Tengo una variable de cadena que representa una ruta de dos por ejemplo:

var = "d:\stuff\morestuff\furtherdown\THEFILE.txt"

Quiero dividir esta cadena en:

[ "d", "stuff", "morestuff", "furtherdown", "THEFILE.txt" ]

He intentado usar split()y replace()pero solo procesan la primera barra diagonal inversa o insertan números hexadecimales en la cadena.

Necesito convertir esta variable de cadena en una cadena sin procesar de alguna manera para poder analizarla.

¿Cuál es la mejor manera de hacer esto?

También debo agregar que el contenido de, vares decir, la ruta que estoy tratando de analizar, es en realidad el valor de retorno de una consulta de línea de comando. No son datos de ruta los que yo mismo genero. Está almacenado en un archivo, y la herramienta de línea de comando no va a escapar de las barras diagonales inversas.


66
Mientras revisa estas respuestas, recuerde que os.path.splitno funciona para usted porque no está escapando de esa cadena correctamente.
Jed Smith

Debe escapar de la cadena o usar una cuerda de hilo: r"d:\stuff\morestuff\furtherdown\THEFILE.txt"para evitar cosas como \sser malinterpretado.
smci

Respuestas:


164

He sido mordido muchas veces por personas que escriben sus propias funciones de violín y se equivocan. Espacios, barras, barras invertidas, dos puntos: las posibilidades de confusión no son infinitas, pero de todos modos los errores se cometen fácilmente. Por lo tanto, soy muy exigente con el uso os.pathy lo recomiendo sobre esa base.

(Sin embargo, el camino hacia la virtud no es el más fácil de tomar, y muchas personas al encontrar esto se sienten tentadas a tomar un camino resbaladizo directo a la condenación. No se darán cuenta hasta que un día todo se haga pedazos, y ellos ... o , lo más probable es que alguien más tenga que averiguar por qué todo salió mal y resulta que alguien creó un nombre de archivo que mezcla barras y barras diagonales inversas, y alguien sugiere que la respuesta es "no hacer eso". t sea cualquiera de estas personas. Excepto por el que mezcló barras y barras invertidas, podrían ser ellas si lo desean).

Puede obtener la unidad y la ruta + archivo de esta manera:

drive, path_and_file = os.path.splitdrive(path)

Obtenga la ruta y el archivo:

path, file = os.path.split(path_and_file)

Obtener los nombres de las carpetas individuales no es especialmente conveniente, pero es el tipo de incomodidad honesta que aumenta el placer de encontrar algo que realmente funciona bien:

folders = []
while 1:
    path, folder = os.path.split(path)

    if folder != "":
        folders.append(folder)
    else:
        if path != "":
            folders.append(path)

        break

folders.reverse()

(Aparece un "\"al comienzo folderssi la ruta era originalmente absoluta. Podría perder un poco de código si no lo deseara).


@brone: prefiero usar esta solución, que tener que preocuparme por escapar de la barra diagonal inversa. ¡Gracias!
BeeBand

1
Me encantaría que me demuestren lo contrario, pero me parece que la solución sugerida no funciona si se usa una ruta como esta "C: \ usr \ rs0 \ my0 \ in111102.log" (a menos que la entrada inicial sea una cadena sin formato )?
shearichard el

1
Parece que esto no dividirá correctamente una ruta si solo contiene un directorio en OSX como "/ ruta / a / mi / carpeta /", para lograr que desee agregar estas dos líneas al principio: if path.endswith("/"):y path = path[:-1].
Kevin London

1
Prefiero la solución de @Tompa
jaycode

1
Estoy de acuerdo con jaycode : la solución de Tompa es el enfoque canónico y debería haber sido la respuesta aceptada. Esta alternativa excesivamente compleja, ineficiente y propensa a errores no pasa al código de producción. No hay una razón razonable para intentar (... y fallar, por supuesto) para analizar iterativamente los nombres de ruta cuando la división simple de cadenas tiene éxito con una sola línea de código.
Cecil Curry

287

yo lo haría

import os
path = os.path.normpath(path)
path.split(os.sep)

Primero normalice la cadena de ruta en una cadena adecuada para el sistema operativo. Entonces os.sepdebe ser seguro de usar como delimitador en la división de la función de cadena.


25
La única respuesta verdadera: ha surgido . La solución canónica es la más simple, por supuesto. ¡Mirad! Porque es elegante y continuo y no tiene bordes insufribles.
Cecil Curry

20
Como una frase,os.path.normpath(a_path).split(os.path.sep)
Daniel Farrell

2
Esto no parece funcionar para path = root. En ese caso, el resultado de path.split es ['', '']. De hecho, en general, esta solución split () da un directorio más a la izquierda con un nombre de cadena vacía (que podría ser reemplazado por la barra inclinada apropiada). El problema central es que una barra inclinada (hacia adelante o hacia atrás según el sistema operativo) es el nombre del directorio raíz, mientras que en otra parte de la ruta es un separador .
gwideman

2
¿Funcionará mejor con un lstrip entonces? os.path.normpath(path).lstrip(os.path.sep).split(os.path.sep)
Vidar

1
@ user60561 Eso se debe a que en Linux, la barra diagonal inversa es un carácter permitido en los nombres de archivo, mientras que en Windows no lo es. Es por eso que en Windows, normpathreconocerá la barra diagonal como un separador. En Linux, normpathsimplemente supondrá que tiene un directorio llamado \1\2y un archivo o directorio llamado dentro 3.
Vojislav Stojkovic

81

Simplemente puede usar el enfoque más pitónico (en mi humilde opinión):

import os

your_path = r"d:\stuff\morestuff\furtherdown\THEFILE.txt"
path_list = your_path.split(os.sep)
print path_list

Lo que te dará:

['d:', 'stuff', 'morestuff', 'furtherdown', 'THEFILE.txt']

La clave aquí es usar en os.seplugar de '\\'o '/', ya que esto lo hace independiente del sistema.

Para eliminar los dos puntos de la letra de la unidad (aunque no veo ninguna razón por la que quiera hacer eso), puede escribir:

path_list[0] = path_list[0][0]

22
Esto funciona some times. Otras veces (al menos en Windows) encontrará rutas que se parecen folder\folder2\folder3/file.txt. Es mejor normalizar primero (os.path.normpath) el camino y luego dividirlo.
vikki

77
Esta respuesta estaba casi allí. Como sugiere vikki , la falla en la normalización de los nombres de ruta antes de que los hechizos de división de cadenas acaben con los casos comunes (por ejemplo, /foo//bar). Vea la respuesta de Tompa para una solución más robusta.
Cecil Curry

62

En Python> = 3.4 esto se ha vuelto mucho más simple. Ahora puede usar pathlib.Path.partspara obtener todas las partes de una ruta.

Ejemplo:

>>> from pathlib import Path
>>> Path('C:/path/to/file.txt').parts
('C:\\', 'path', 'to', 'file.txt')
>>> Path(r'C:\path\to\file.txt').parts
('C:\\', 'path', 'to', 'file.txt')

En una instalación de Windows de Python 3, esto supondrá que está trabajando con rutas de Windows, y en * nix asumirá que está trabajando con rutas posix. Esto suele ser lo que desea, pero si no es así, puede usar las clases pathlib.PurePosixPatho pathlib.PureWindowsPathsegún sea necesario:

>>> from pathlib import PurePosixPath, PureWindowsPath
>>> PurePosixPath('/path/to/file.txt').parts
('/', 'path', 'to', 'file.txt')
>>> PureWindowsPath(r'C:\path\to\file.txt').parts
('C:\\', 'path', 'to', 'file.txt')
>>> PureWindowsPath(r'\\host\share\path\to\file.txt').parts
('\\\\host\\share\\', 'path', 'to', 'file.txt')

Editar: También hay un backport para python 2 disponible: pathlib2


3
Path.parts es lo que siempre he querido, pero nunca supe que existía hasta hoy.
JamEnergy

¿Por qué no se ha envuelto esto en una bonita función nativa de Python?
Eduardo Pignatelli

2
Esa es la respuesta!
nayriz

11

El problema aquí comienza con cómo estás creando la cadena en primer lugar.

a = "d:\stuff\morestuff\furtherdown\THEFILE.txt"

Hecho de esta manera, Python está tratando de caso especial siguientes: \s, \m, \f, y \T. En su caso, \fse trata como un avance de página (0x0C) mientras que las demás barras invertidas se manejan correctamente. Lo que debe hacer es uno de estos:

b = "d:\\stuff\\morestuff\\furtherdown\\THEFILE.txt"      # doubled backslashes
c = r"d:\stuff\morestuff\furtherdown\THEFILE.txt"         # raw string, no doubling necessary

Luego, una vez que separe cualquiera de estos, obtendrá el resultado que desea.


@W. Craig Trader, gracias, pero este camino no es uno que yo mismo genero, me lo devuelve otro programa y tengo que almacenar estos datos en una variable. No estoy seguro de cómo convertir los datos almacenados en una variable en "texto sin formato".
BeeBand

No existe tal cosa como un "texto sin formato" ... es solo cómo lo representa en la fuente. Prefiera r "" a la cadena o páselo a través de .replace ('\\', '/')
Marco Mariani

@BeeBand, ¿cómo está recuperando los datos del otro programa? ¿Lo estás leyendo desde un archivo, una tubería, un enchufe? Si es así, entonces no necesitas hacer nada elegante; La única razón para duplicar las barras invertidas o utilizar cadenas sin procesar es colocar constantes de cadena en el código Python. Por otro lado, si el otro programa está generando barras invertidas dobles, entonces querrás limpiar eso antes de dividir tu camino.
Craig Trader

@W. Craig Trader: lo estoy leyendo de un archivo que otro programa ha escrito. No pude conseguir split()o replace()al trabajo por alguna razón - me hacía cada vez valores hex. Sin embargo, tienes razón, creo que estaba ladrando el árbol equivocado con la idea de cadena cruda; creo que solo estaba usando split()incorrectamente. Porque probé algunas de estas soluciones split()y ahora funcionan para mí.
BeeBand

10

Para una solución algo más concisa, considere lo siguiente:

def split_path(p):
    a,b = os.path.split(p)
    return (split_path(a) if len(a) and len(b) else []) + [b]

Esta es mi solución favorita para este problema. Muy agradable.
Will Moore

1
Esto no funciona si la ruta termina con /. Además, le da una cadena vacía al comienzo de la lista si su ruta comienza con/
Sorig

4

En realidad, no puedo aportar una respuesta real a esta (ya que vine aquí con la esperanza de encontrarla yo mismo), pero para mí la cantidad de enfoques diferentes y todas las advertencias mencionadas es el indicador más seguro de que el módulo os.path de Python necesita desesperadamente esto como una función incorporada.


4

La forma funcional, con un generador .

def split(path):
    (drive, head) = os.path.splitdrive(path)
    while (head != os.sep):
        (head, tail) = os.path.split(head)
        yield tail

En acción:

>>> print([x for x in split(os.path.normpath('/path/to/filename'))])
['filename', 'to', 'path']

3

Esto funciona para mi:

>>> a=r"d:\stuff\morestuff\furtherdown\THEFILE.txt"
>>> a.split("\\")
['d:', 'stuff', 'morestuff', 'furtherdown', 'THEFILE.txt']

Claro que es posible que también necesite quitar el colon del primer componente, pero mantenerlo hace posible volver a ensamblar la ruta.

El rmodificador marca el literal de cadena como "sin procesar"; observe cómo las barras invertidas incrustadas no se duplican.


@unwind: al rfrente de tu cadena, ¿a qué se refiere eso?
BeeBand

2
r significa cadena sin procesar: escapa automáticamente a los \ caracteres. Es útil para usar siempre que estés haciendo rutas.
Wayne Werner el

1
@BeeBand: no necesitas preocuparte; la r "" es solo algo que importa durante la compilación / análisis del código, no es algo que se convierte en una propiedad de la cadena una vez analizada. Simplemente significa "aquí hay un literal de cadena, pero no interprete ninguna barra diagonal inversa como si tuviera otro significado que no sea barra diagonal inversa".
relajarse

3
Creo que podría ser útil mencionarlo menos, ¿bien hacerlo más ambiguo usando a.split (os.sep) en lugar de codificarlo?
Tim McJilton el

44
Tengo que rechazarlo por perder la oportunidad de explicar os.path.splity os.pathsep, considerando que ambos son mucho más portátiles de lo que ha escrito. Puede que no le importe a OP ahora, pero lo hará cuando esté escribiendo algo que necesite mover plataformas.
Jed Smith

3

Las cosas acerca de sobre mypath.split("\\")se expresarían mejor como mypath.split(os.sep). sepes el separador de ruta para su plataforma particular (por ejemplo, \para Windows, /para Unix, etc.), y la compilación de Python sabe cuál usar. Si lo usa sep, entonces su código será independiente de la plataforma.


1
O os.path.split. Desea tener cuidado os.pathsep, porque está :en mi versión de Python en OS X (y os.path.splitse maneja correctamente /).
Jed Smith

44
Quieres decir que os.sepno os.pathsep. Siga la sabiduría de los os.sepdocumentos: tenga en cuenta que saber esto no es suficiente para poder analizar o concatenar nombres de ruta: use os.path.split () y os.path.join ().
Jon-Eric

1

re.split () puede ayudar un poco más que string.split ()

import re    
var = "d:\stuff\morestuff\furtherdown\THEFILE.txt"
re.split( r'[\\/]', var )
['d:', 'stuff', 'morestuff', 'furtherdown', 'THEFILE.txt']

Si también desea admitir rutas de Linux y Mac, simplemente agregue el filtro (Ninguno, resultado), para que elimine el '' no deseado de la división () ya que sus rutas comienzan con '/' o '//'. por ejemplo '// mount / ...' o '/ var / tmp /'

import re    
var = "/var/stuff/morestuff/furtherdown/THEFILE.txt"
result = re.split( r'[\\/]', var )
filter( None, result )
['var', 'stuff', 'morestuff', 'furtherdown', 'THEFILE.txt']

1

Puedes recursivamente os.path.splitla cadena

import os
def parts(path):
    p,f = os.path.split(path)
    return parts(p) + [f] if f else [p]

Probar esto contra algunas cadenas de ruta y volver a montar la ruta con os.path.join

>>> for path in [
...         r'd:\stuff\morestuff\furtherdown\THEFILE.txt',
...         '/path/to/file.txt',
...         'relative/path/to/file.txt',
...         r'C:\path\to\file.txt',
...         r'\\host\share\path\to\file.txt',
...     ]:
...     print parts(path), os.path.join(*parts(path))
... 
['d:\\', 'stuff', 'morestuff', 'furtherdown', 'THEFILE.txt'] d:\stuff\morestuff\furtherdown\THEFILE.txt
['/', 'path', 'to', 'file.txt'] /path\to\file.txt
['', 'relative', 'path', 'to', 'file.txt'] relative\path\to\file.txt
['C:\\', 'path', 'to', 'file.txt'] C:\path\to\file.txt
['\\\\', 'host', 'share', 'path', 'to', 'file.txt'] \\host\share\path\to\file.txt

Es posible que el primer elemento de la lista deba tratarse de manera diferente según cómo desee tratar las letras de unidad, las rutas UNC y las rutas absolutas y relativas. Cambiar el último [p]a [os.path.splitdrive(p)]fuerza el problema dividiendo la letra de unidad y la raíz del directorio en una tupla.

import os
def parts(path):
    p,f = os.path.split(path)
    return parts(p) + [f] if f else [os.path.splitdrive(p)]

[('d:', '\\'), 'stuff', 'morestuff', 'furtherdown', 'THEFILE.txt']
[('', '/'), 'path', 'to', 'file.txt']
[('', ''), 'relative', 'path', 'to', 'file.txt']
[('C:', '\\'), 'path', 'to', 'file.txt']
[('', '\\\\'), 'host', 'share', 'path', 'to', 'file.txt']

Editar: me he dado cuenta de que esta respuesta es muy similar a la dada anteriormente por el usuario1556435 . Dejo mi respuesta, ya que el manejo del componente de unidad de la ruta es diferente.


0

Al igual que otros explicaron: su problema surgió del uso \, que es el carácter de escape en cadena literal / constante. OTOH, si tuviera esa cadena de ruta de archivo de otra fuente (leída desde un archivo, consola o devuelta por la función os), no habría habido problemas para dividirse en '\\' o r '\'.

Y al igual que otros sugirieron, si desea usar \en el literal del programa, debe duplicarlo \\o todo el literal debe tener el prefijo r, así r'lite\ral'o r"lite\ral"para evitar que el analizador convierta eso \y el carácter rCR (retorno de carro).

Sin embargo, hay una forma más: ¡simplemente no use \nombres de ruta de barra invertida en su código! Desde el siglo pasado, Windows reconoce y funciona bien con nombres de ruta que usan barra diagonal como separador de directorios /. De alguna manera, no mucha gente lo sabe ... pero funciona:

>>> var = "d:/stuff/morestuff/furtherdown/THEFILE.txt"
>>> var.split('/')
['d:', 'stuff', 'morestuff', 'furtherdown', 'THEFILE.txt']

Por cierto, esto hará que su código funcione en Unix, Windows y Mac ... porque todos ellos se usan /como separador de directorios ... incluso si no desea usar las constantes predefinidas del módulo os.


Desafortunadamente, los datos me son devueltos por otro programa que ejecuto desde mi script de Python. No tengo ningún control sobre si usar '\' o '/': es el programa de terceros el que determina esto (probablemente en una plataforma).
BeeBand

@BeeBand: Ah, entonces no tendrá el problema que experimentó durante las pruebas, cuando proporcionó la cadena como literal en su programa. O puede hacer el siguiente truco malvado después de recibir el camino: var = var.replace('\\','/')- reemplace \ con / y continúe trabajando solo con barras diagonales :)
Nas Banov

eso es realmente un truco malvado: o)
BeeBand

@BeeBand: por eso lo advertí. Cuando digo que algo es malo, no necesariamente quiero decir que nunca debe usarse, pero uno debe ser muy consciente de por qué lo está usando y alerta de las consecuencias no deseadas. En este caso, una consecuencia muy poco probable es que si se usa en el sistema de archivos Unix con `` uso en nombre de archivo o directorio (es realmente difícil pero posible), este código se 'romperá'
Nas Banov

0

Supongamos que tiene un archivo filedata.txtcon contenido:

d:\stuff\morestuff\furtherdown\THEFILE.txt
d:\otherstuff\something\otherfile.txt

Puede leer y dividir las rutas de archivo:

>>> for i in open("filedata.txt").readlines():
...     print i.strip().split("\\")
... 
['d:', 'stuff', 'morestuff', 'furtherdown', 'THEFILE.txt']
['d:', 'otherstuff', 'something', 'otherfile.txt']

esto realmente funciona, gracias! Pero elegí la solución de Brone porque prefiero no preocuparme por escapar de la barra invertida.
BeeBand

9
No es pitónico, ya que depende del sistema de archivos.
jb.

0

Uso lo siguiente, ya que usa la función os.path.basename, no agrega barras a la lista devuelta. También funciona con barras inclinadas de cualquier plataforma: es decir, windows \ \ o unix's /. Y además, no agrega el \\\\ que Windows usa para las rutas del servidor :)

def SplitPath( split_path ):
    pathSplit_lst   = []
    while os.path.basename(split_path):
        pathSplit_lst.append( os.path.basename(split_path) )
        split_path = os.path.dirname(split_path)
    pathSplit_lst.reverse()
    return pathSplit_lst

Entonces, para '\\\\ server \\ folder1 \\ folder2 \\ folder3 \\ folder4'

usted obtiene

['servidor', 'carpeta1', 'carpeta2', 'carpeta3', 'carpeta4']


1
Eso no sigue a la invariante a la que pasar el resultado os.path.join()debería devolver la cadena original. Yo diría que la salida correcta para su entrada de ejemplo es [r'\\','server','folder1','folder2','folder3','folder4']. Es decir, qué os.path.split()hace.
Jon-Eric

0

No estoy seguro de si esto responde completamente a la pregunta, pero me divertí mucho escribiendo esta pequeña función que mantiene una pila, se adhiere a las manipulaciones basadas en os.path y devuelve la lista / pila de elementos.

  9 def components(path):
 10     ret = []
 11     while len(path) > 0:
 12         path, crust = split(path)
 13         ret.insert(0, crust)
 14
 15     return ret
 16

0

Debajo de la línea de código puede manejar:

  1. C: / ruta / ruta
  2. C: // ruta // ruta
  3. C: \ ruta \ ruta
  4. C: \ ruta \ ruta

ruta = re.split (r '[/// \]', ruta)


0

Uno recursivo para la diversión.

No es la respuesta más elegante, pero debería funcionar en todas partes:

import os

def split_path(path):
    head = os.path.dirname(path)
    tail = os.path.basename(path)
    if head == os.path.dirname(head):
        return [tail]
    return split_path(head) + [tail]

De hecho, lo siento. Debería haber leído cuidadosamente la pregunta ... un camino 'dos'.
DuGNu

-1

utilizar ntpath.split()


cuando uso os.path.split () obtengo, ( d:\\stuff, morestuff\x0curtherdown\thefile.mux)
BeeBand

Como señaló BeeBand, os.path.split () realmente no hace lo deseado.
Descansa el

lo siento, me acabo de dar cuenta de que os.path solo funciona dependiendo de tu sistema operativo. ntpath analizará dos caminos.
deft_code el

incluso con ntpath sigo teniendod:\\stuff, morestuff\x0curtherdown\thefile.mux
BeeBand 02 de

2
@BeeBand: tienes problemas para escapar de tu cadena. '\x0c'es el carácter del feed del formulario. La forma de crear el carácter de fuente de formulario es '\ f'. Si realmente desea la cadena literal '\ f', tiene dos opciones: '\\f'o r'\f'.
deft_code el
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.