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.

BeeBand
fuente
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).

HunnyBear
fuente
@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.

Tompa
fuente
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]
Maciek D.
fuente
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

Freidrichen
fuente
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.

Craig Trader
fuente
@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]
usuario1556435
fuente
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.

antred
fuente
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']
Benoit
fuente
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.

relajarse
fuente
@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.

Chris
fuente
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']
Asi
fuente
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.

Mike Robins
fuente
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.

Nas Banov
fuente
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']
zoli2k
fuente
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']

Arrendajo
fuente
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
mallyvai
fuente
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)

Gour Bera
fuente
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]
DuGNu
fuente
De hecho, lo siento. Debería haber leído cuidadosamente la pregunta ... un camino 'dos'.
DuGNu
-1

utilizar ntpath.split()

código_deft
fuente
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