Extraiga el nombre del archivo de la ruta, sin importar el formato os / ruta

794

¿Qué biblioteca de Python puedo usar para extraer nombres de archivos de rutas, sin importar cuál sea el sistema operativo o el formato de ruta?

Por ejemplo, me gustaría que todos estos caminos me devuelvan c:

a/b/c/
a/b/c
\a\b\c
\a\b\c\
a\b\c
a/b/../../a/b/c/
a/b/../../a/b/c
Zumbido
fuente

Respuestas:

781

Usar os.path.splito, os.path.basenamecomo lo sugieren otros, no funcionará en todos los casos: si está ejecutando el script en Linux e intenta procesar una ruta clásica al estilo de Windows, fallará.

Las rutas de Windows pueden usar barra diagonal inversa o barra diagonal como separador de ruta. Por lo tanto, el ntpathmódulo (que es equivalente a os.path cuando se ejecuta en Windows) funcionará para todas las (1) rutas en todas las plataformas.

import ntpath
ntpath.basename("a/b/c")

Por supuesto, si el archivo termina con una barra oblicua, el nombre base estará vacío, así que haga su propia función para tratarlo:

def path_leaf(path):
    head, tail = ntpath.split(path)
    return tail or ntpath.basename(head)

Verificación:

>>> paths = ['a/b/c/', 'a/b/c', '\\a\\b\\c', '\\a\\b\\c\\', 'a\\b\\c', 
...     'a/b/../../a/b/c/', 'a/b/../../a/b/c']
>>> [path_leaf(path) for path in paths]
['c', 'c', 'c', 'c', 'c', 'c', 'c']


(1) Hay una advertencia: los nombres de archivo de Linux pueden contener barras invertidas . Entonces, en Linux, r'a/b\c'siempre se refiere al archivo b\cen la acarpeta, mientras que en Windows, siempre se refiere al carchivo en la bsubcarpeta de la acarpeta. Entonces, cuando se utilizan barras diagonales hacia adelante y hacia atrás en una ruta, debe conocer la plataforma asociada para poder interpretarla correctamente. En la práctica, generalmente es seguro asumir que es una ruta de Windows, ya que las barras invertidas rara vez se usan en los nombres de archivos de Linux, pero tenga esto en cuenta cuando codifique para que no cree agujeros de seguridad accidentales.

Lauritz V. Thaulow
fuente
29
en Windows, os.pathsolo carga el ntpathmódulo internamente. Con este módulo, es posible manejar los '\\'separadores de ruta incluso en máquinas Linux. Para Linux, el posixpathmódulo (resp. os.path) Simplificará las operaciones de ruta para permitir solo '/'separadores de estilo posix .
moooeeeep
@moooeeeep ¿Entonces podríamos usar la respuesta de Stranac, y es confiable? ( "Usar os.path.split u os.path.basename como otros sugieren no funcionará en todos los casos: si está ejecutando el script en Linux e intenta procesar una ruta clásica al estilo de Windows, fallará" - - la cita es de la publicación de Lauritz - y no entiendo, esta advertencia se refiere a la respuesta de Stranac, o no).
John Cj
3
@ johnc.j. Solo cuando necesita analizar rutas de estilo de Windows (por ejemplo, r'C:\path\to\file.txt') en una máquina Linux, necesita usar el módulo ntpath. De lo contrario, puede usar las funciones de os.path. Esto se debe a que los sistemas Linux normalmente permiten el uso de los caracteres de barra diagonal inversa en los nombres de archivo (como se explica en la respuesta).
moooeeeep
2
¿No es su solución equivalente a os.path.basename(os.path.normpath(path))?
Mr_and_Mrs_D
2
Por lo que vale para futuros visitantes a esta pregunta, me encontré con la situación de la que Lauritz estaba advirtiendo y su solución fue la única que funcionó. Ningún financiamiento con sistema operativo podría generar solo el nombre del archivo. En mi opinión, ntpath es el camino a seguir.
Harabeck
1250

En realidad, hay una función que devuelve exactamente lo que quieres

import os
print(os.path.basename(your_path))
Stranac
fuente
22
Si desea procesar rutas de manera independiente del sistema operativo, entonces para os.path.basename (u "C: \\ temp \\ bla.txt") espera obtener 'bla.txt'. La pregunta no se trata de obtener un nombre de archivo válido, sino de extraer el nombre de una ruta.
Adi Roiban
3
En mi búsqueda de Google para encontrar el nombre de archivo de una ruta, esta respuesta fue la más útil. Mi caso de uso solo está en Windows de todos modos.
Bobort
2
os.path.basename(your_path)Esto funcionó! Quería ruta del script: os.path.dirname(os.path.realpath(__file__))y el nombre del script: os.path.basename(os.path.realpath(__file__)). ¡Gracias!
TheWalkingData
@AdiRoiban ¿Podría por favor elaborar su comentario? Lo he comprobado en Windows 7 y en realidad me sale "bla.txt' Simplemente decir, no veo ningún problema (para mí)..
John cj
10
@ johnc.j. El punto es que cuando intentaste esto en Linux, obtendrías en su 'C:\\temp\\bla.txt'lugar.
moooeeeep
218

os.path.split es la función que busca

head, tail = os.path.split("/tmp/d/a.dat")

>>> print(tail)
a.dat
>>> print(head)
/tmp/d
Jakob Bowyer
fuente
40
Solo para que otros usuarios tengan cuidado, esto devuelve "" si la ruta termina en "/" o "\"
BuZz
Cuando intento "C: \ Users \ Dell \ Desktop \ ProjectShadow \ button \ button.py", devuelve este "ProjectShadow utton tton" para todo lo que no sea esto, devuelve el resultado correcto
amitnair92
44
@ amitnair92: haga esto: r "C: \ Users \ Dell \ Desktop \ ProjectShadow \ button \ button.py" o esto: "C: \\ Users \\ Dell \\ Desktop \\ ProjectShadow \\ button \\ button .py "-" \ b "es un carácter especial (creo que 'campana' del sistema), similar a cómo \ r o \ n significan nueva línea / retorno de carro. Prefijar la cadena con r "C: \ ..." significa usar la entrada sin procesar dada
Bruce Lamond
87

En python 3

>>> from pathlib import Path    
>>> Path("/tmp/d/a.dat").name
'a.dat'
Kishan B
fuente
3.4 a 3.6 o posterior, dependiendo exactamente qué elementos pathlib usa.
LightCC
8
también puede usar Path ("some / path / to / file.dat"). stem para obtener el nombre de archivo sin la extensión del archivo
s2t2
47
import os
head, tail = os.path.split('path/to/file.exe')

cola es lo que quieres, el nombre del archivo.

Consulte los documentos del módulo del sistema operativo Python para obtener más detalles.

número 5
fuente
13
Solo para que otros usuarios tengan cuidado, esto devuelve "" si la ruta termina en "/" o "\"
BuZz
19
import os
file_location = '/srv/volume1/data/eds/eds_report.csv'
file_name = os.path.basename(file_location )  #eds_report.csv
location = os.path.dirname(file_location )    #/srv/volume1/data/eds
Saurabh Chandra Patel
fuente
12

En su ejemplo, también necesitará quitar una barra diagonal del lado derecho para regresar c:

>>> import os
>>> path = 'a/b/c/'
>>> path = path.rstrip(os.sep) # strip the slash from the right side
>>> os.path.basename(path)
'c'

Segundo nivel:

>>> os.path.filename(os.path.dirname(path))
'b'

Actualización: creo que lazyrha proporcionado la respuesta correcta. Mi código no funcionará con rutas similares a Windows en sistemas Unix y viceversa frente a rutas similares a Unix en sistemas Windows.

Esquí
fuente
Su respuesta no funcionará r"a\b\c"en Linux, ni "a/b/c"en Windows.
Lauritz V. Thaulow
por supuesto, os.path.basename(path)solo funcionará si os.path.isfile(path)es así True. Por path = 'a/b/c/'lo tanto, no es un nombre de archivo válido en absoluto ...
moooeeeep 05 de
1
@fmaas os.path.basename es puramente una función de procesamiento de cadenas. No le importa si el archivo existe o si es un archivo o directorio. os.path.basename("a/b/c/")vuelve ""debido a la barra inclinada final.
Lauritz V. Thaulow
lazyr¡tienes razón! No pensé en eso. ¿Sería seguro hacerlo solo path = path.replace('\\', '/')?
Ski
@Skirmantas, supongo, pero no se siente bien. Creo que el procesamiento de ruta debe hacerse con las herramientas integradas que se hicieron para el trabajo. Hay mucho más en los caminos de lo que parece.
Lauritz V. Thaulow
11
fname = str("C:\Windows\paint.exe").split('\\')[-1:][0]

esto devolverá: paint.exe

cambie el valor de la función de división con respecto a su ruta o sistema operativo.

Eslam Hamouda
fuente
Esta es la respuesta que me gustó, pero ¿por qué no hacer lo siguiente? fname = str(path).split('/')[-1]
asultan904
10

Si desea obtener el nombre de archivo automáticamente, puede hacerlo

import glob

for f in glob.glob('/your/path/*'):
    print(os.path.split(f)[-1])
vinu
fuente
8

Si la ruta del archivo no terminó con "/" y los directorios están separados por "/", utilice el siguiente código. Como sabemos en general, la ruta no termina con "/".

import os
path_str = "/var/www/index.html"
print(os.path.basename(path_str))

Pero en algunos casos, como las URL terminan con "/", utilice el siguiente código

import os
path_str = "/home/some_str/last_str/"
split_path = path_str.rsplit("/",1)
print(os.path.basename(split_path[0]))

pero cuando su ruta se deletrea con "\", que generalmente se encuentra en las rutas de Windows, puede usar los siguientes códigos

import os
path_str = "c:\\var\www\index.html"
print(os.path.basename(path_str))

import os
path_str = "c:\\home\some_str\last_str\\"
split_path = path_str.rsplit("\\",1)
print(os.path.basename(split_path[0]))

Puede combinar ambos en una sola función marcando el tipo de sistema operativo y devolviendo el resultado.

Santosh kumar Manda
fuente
7

Esto también funciona para Linux y Windows con la biblioteca estándar

paths = ['a/b/c/', 'a/b/c', '\\a\\b\\c', '\\a\\b\\c\\', 'a\\b\\c',
         'a/b/../../a/b/c/', 'a/b/../../a/b/c']

def path_leaf(path):
    return path.strip('/').strip('\\').split('/')[-1].split('\\')[-1]

[path_leaf(path) for path in paths]

Resultados:

['c', 'c', 'c', 'c', 'c', 'c', 'c']
Csabka
fuente
6

Aquí hay una solución de expresiones regulares, que parece funcionar con cualquier ruta de sistema operativo en cualquier sistema operativo.

No se necesita ningún otro módulo, y tampoco se necesita preprocesamiento:

import re

def extract_basename(path):
  """Extracts basename of a given path. Should Work with any OS Path on any OS"""
  basename = re.search(r'[^\\/]+(?=[\\/]?$)', path)
  if basename:
    return basename.group(0)


paths = ['a/b/c/', 'a/b/c', '\\a\\b\\c', '\\a\\b\\c\\', 'a\\b\\c',
         'a/b/../../a/b/c/', 'a/b/../../a/b/c']

print([extract_basename(path) for path in paths])
# ['c', 'c', 'c', 'c', 'c', 'c', 'c']


extra_paths = ['C:\\', 'alone', '/a/space in filename', 'C:\\multi\nline']

print([extract_basename(path) for path in extra_paths])
# ['C:', 'alone', 'space in filename', 'multi\nline']

Actualizar:

Si sólo desea un potencial de nombre de archivo, si está presente (es decir, /a/b/es un directorio y también lo es c:\windows\), cambiar la expresión regular a: r'[^\\/]+(?![\\/])$'. Para el "regex desafiado", esto cambia la búsqueda anticipada positiva para algún tipo de barra diagonal a una vista anticipada negativa, lo que hace que los nombres de ruta que terminan con dicha barra no devuelvan nada en lugar del último subdirectorio en la ruta. Por supuesto, no hay garantía de que el nombre de archivo potencial realmente se refiera a un archivo y para eso os.path.is_dir()o os.path.is_file()deba emplearse.

Esto coincidirá de la siguiente manera:

/a/b/c/             # nothing, pathname ends with the dir 'c'
c:\windows\         # nothing, pathname ends with the dir 'windows'
c:hello.txt         # matches potential filename 'hello.txt'
~it_s_me/.bashrc    # matches potential filename '.bashrc'
c:\windows\system32 # matches potential filename 'system32', except
                    # that is obviously a dir. os.path.is_dir()
                    # should be used to tell us for sure

La expresión regular se puede probar aquí .

Eric Duminil
fuente
está utilizando re, ¿por qué no el módulo os?
Saurabh Chandra Patel
@SaurabhChandraPatel ha pasado mucho tiempo. Si no recuerdo mal, regex se usa como una solución multiplataforma en este caso. Puede procesar nombres de archivos de Windows en un servidor Linux, por ejemplo.
Eric Duminil
5

Tal vez solo mi solución todo en uno sin importantes novedades (considere el archivo temporal para crear archivos temporales: D)

import tempfile
abc = tempfile.NamedTemporaryFile(dir='/tmp/')
abc.name
abc.name.replace("/", " ").split()[-1] 

Obtener los valores de abc.nameserá una cadena como esta: '/tmp/tmpks5oksk7' así puedo reemplazar el /con un espacio .replace("/", " ")y luego llamar split(). Eso devolverá una lista y obtengo el último elemento de la lista con[-1]

No es necesario importar ningún módulo.

Akendo
fuente
2
¿Qué pasa si el nombre de archivo o un directorio contiene un espacio?
kriss
1
¿Qué pasa con una división directa ("/") [- 1]?
Nan
4

Nunca he visto caminos con doble barra invertida, ¿existen? La característica incorporada del módulo python osfalla para aquellos. Todos los demás funcionan, también la advertencia dada por usted con os.path.normpath():

paths = ['a/b/c/', 'a/b/c', '\\a\\b\\c', '\\a\\b\\c\\', 'a\\b\\c', 
...     'a/b/../../a/b/c/', 'a/b/../../a/b/c', 'a/./b/c', 'a\b/c']
for path in paths:
    os.path.basename(os.path.normpath(path))
PythoNic
fuente
Esos no son dobles backslahes. Son barras invertidas simples, y necesitan ser escapadas.
Eric Duminil
3

El separador de Windows puede estar en un nombre de archivo Unix o ruta de Windows. El separador de Unix solo puede existir en la ruta de Unix. La presencia de un separador de Unix indica una ruta que no es de Windows.

Lo siguiente eliminará (cortará el separador final) por el separador específico del sistema operativo, luego se dividirá y devolverá el valor más a la derecha. Es feo, pero simple basado en la suposición anterior. Si la suposición es incorrecta, actualice y actualizaré esta respuesta para que coincida con las condiciones más precisas.

a.rstrip("\\\\" if a.count("/") == 0 else '/').split("\\\\" if a.count("/") == 0 else '/')[-1]

Código de muestra:

b = ['a/b/c/','a/b/c','\\a\\b\\c','\\a\\b\\c\\','a\\b\\c','a/b/../../a/b/c/','a/b/../../a/b/c']

for a in b:

    print (a, a.rstrip("\\" if a.count("/") == 0 else '/').split("\\" if a.count("/") == 0 else '/')[-1])
dusc2don
fuente
1
Además, no dude en enviarme sugerencias sobre cómo formatear en este lugar. Tomó media docena de intentos para obtener el código de muestra en su lugar.
dusc2don
1

Para completar, aquí está la pathlibsolución para python 3.2+:

>>> from pathlib import PureWindowsPath

>>> paths = ['a/b/c/', 'a/b/c', '\\a\\b\\c', '\\a\\b\\c\\', 'a\\b\\c', 
...          'a/b/../../a/b/c/', 'a/b/../../a/b/c']

>>> [PureWindowsPath(path).name for path in paths]
['c', 'c', 'c', 'c', 'c', 'c', 'c']

Esto funciona tanto en Windows como en Linux.

Morgoth
fuente
1

En Python 2 y 3, usando el módulo pathlib2 :

import posixpath  # to generate unix paths
from pathlib2 import PurePath, PureWindowsPath, PurePosixPath

def path2unix(path, nojoin=True, fromwinpath=False):
    """From a path given in any format, converts to posix path format
    fromwinpath=True forces the input path to be recognized as a Windows path (useful on Unix machines to unit test Windows paths)"""
    if not path:
        return path
    if fromwinpath:
        pathparts = list(PureWindowsPath(path).parts)
    else:
        pathparts = list(PurePath(path).parts)
    if nojoin:
        return pathparts
    else:
        return posixpath.join(*pathparts)

Uso:

In [9]: path2unix('lala/lolo/haha.dat')
Out[9]: ['lala', 'lolo', 'haha.dat']

In [10]: path2unix(r'C:\lala/lolo/haha.dat')
Out[10]: ['C:\\', 'lala', 'lolo', 'haha.dat']

In [11]: path2unix(r'C:\lala/lolo/haha.dat') # works even with malformatted cases mixing both Windows and Linux path separators
Out[11]: ['C:\\', 'lala', 'lolo', 'haha.dat']

Con tu caso de prueba:

In [12]: testcase = paths = ['a/b/c/', 'a/b/c', '\\a\\b\\c', '\\a\\b\\c\\', 'a\\b\\c',
    ...: ...     'a/b/../../a/b/c/', 'a/b/../../a/b/c']

In [14]: for t in testcase:
    ...:     print(path2unix(t)[-1])
    ...:
    ...:
c
c
c
c
c
c
c

La idea aquí es convertir todas las rutas en la representación interna unificada de pathlib2, con diferentes decodificadores dependiendo de la plataforma. Afortunadamente, pathlib2incluye un decodificador genérico llamado PurePathque debería funcionar en cualquier ruta. En caso de que esto no funcione, puede forzar el reconocimiento de la ruta de Windows utilizando fromwinpath=True. Esto dividirá la cadena de entrada en partes, la última es la hoja que está buscando, de ahí la path2unix(t)[-1].

Si el argumento es nojoin=False, la ruta se volverá a unir, por lo que la salida es simplemente la cadena de entrada convertida a un formato Unix, que puede ser útil para comparar subrutas en las plataformas.

gaborous
fuente