Extraia o nome do arquivo do caminho, independentemente do formato os / path

794

Qual biblioteca Python posso usar para extrair nomes de arquivos de caminhos, independentemente do sistema operacional ou do formato do caminho?

Por exemplo, eu gostaria que todos esses caminhos me retornassem 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
fonte

Respostas:

781

Usar os.path.splitou os.path.basenamecomo outros sugerem não funcionará em todos os casos: se você estiver executando o script no Linux e tentar processar um caminho clássico no estilo Windows, ele falhará.

Os caminhos do Windows podem usar barra invertida ou barra invertida como separador de caminho. Portanto, o ntpathmódulo (que é equivalente ao os.path ao executar no Windows) funcionará para todos os (1) caminhos em todas as plataformas.

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

Obviamente, se o arquivo terminar com uma barra, o nome da base ficará vazio; portanto, faça sua própria função para lidar com isso:

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

Verificação:

>>> 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) Há uma ressalva: os nomes de arquivos do Linux podem conter barras invertidas . Portanto, no linux, r'a/b\c'sempre se refere ao arquivo b\cna apasta, enquanto no Windows, sempre se refere ao carquivo na bsubpasta da apasta. Portanto, quando ambas as barras são usadas em um caminho, você precisa conhecer a plataforma associada para poder interpretá-la corretamente. Na prática, geralmente é seguro assumir que é um caminho do Windows, já que as barras invertidas raramente são usadas nos nomes de arquivos do Linux, mas lembre-se disso quando você codificar para não criar falhas acidentais na segurança.

Lauritz V. Thaulow
fonte
29
no Windows, os.pathbasta carregar o ntpathmódulo internamente. Usando este módulo, é possível manipular os '\\'separadores de caminho mesmo em máquinas Linux. Para Linux, o posixpathmódulo (resp. os.path) Simplificará as operações do caminho para permitir apenas '/'separadores de estilo posix .
precisa
@moooeeeep Para que pudéssemos usar a resposta de Stranac, e ela é confiável? ( "Usar os.path.split ou os.path.basename como outros sugerem não funcionará em todos os casos: se você estiver executando o script no Linux e tentar processar um caminho clássico no estilo do Windows, ele falhará" - - a citação é do post de Lauritz - e eu não entendo, esse aviso diz respeito à resposta de Stranac, ou não).
John cj
3
@ johnc.j. Somente quando você precisar analisar os caminhos no estilo do Windows (por exemplo, r'C:\path\to\file.txt') em uma máquina Linux, precisará usar o módulo ntpath. Caso contrário, você pode usar as funções do os.path. Isso ocorre porque os sistemas Linux normalmente permitem o uso dos caracteres de barra invertida nos nomes de arquivos (conforme explicado na resposta).
precisa saber é o seguinte
2
Sua solução não é equivalente os.path.basename(os.path.normpath(path))?
Mr_and_Mrs_D
2
Pelo que vale a pena para futuros visitantes dessa pergunta, eu me deparei com a situação que Lauritz estava avisando e sua solução foi a única que funcionou. Nenhum financiamento com o OS poderia gerar apenas o nome do arquivo. Então, imho, ntpath é o caminho a percorrer.
Harabeck
1250

Na verdade, há uma função que retorna exatamente o que você deseja

import os
print(os.path.basename(your_path))
Stranac
fonte
22
Se você deseja processar caminhos de maneira independente do SO, então para os.path.basename (u "C: \\ temp \\ bla.txt"), você espera obter 'bla.txt'. A questão não é obter um nome de arquivo válido, mas extrair o nome de um caminho.
Adi Roiban
3
Na minha pesquisa no Google para encontrar o nome do arquivo de um caminho, esta resposta foi a mais útil. Meu caso de uso está apenas no Windows de qualquer maneira.
Bobort
2
os.path.basename(your_path)Isso funcionou! Eu queria caminho do script: os.path.dirname(os.path.realpath(__file__))eo nome do script: os.path.basename(os.path.realpath(__file__)). Obrigado!
TheWalkingData
@AdiRoiban Você poderia elaborar seu comentário? Eu testei no Windows 7 e realmente recebo "bla.txt '. Simplesmente dizendo, não vejo nenhum problema (para mim)."
joão cj
10
@ johnc.j. O ponto é que, quando você tentava fazer isso no Linux, conseguia 'C:\\temp\\bla.txt'.
moooeeeep
218

os.path.split é a função que você está procurando

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

>>> print(tail)
a.dat
>>> print(head)
/tmp/d
Jakob Bowyer
fonte
40
Apenas para que outros usuários sejam cuidadosos, isso retornará "" se os caminhos terminarem em "/" ou "\"
BuZz
Quando tento "C: \ Users \ Dell \ Desktop \ ProjectShadow botão \ \ button.py" ele retorna thi "ProjectShadow utton tton" por tudo o que não seja isso, voltar resultado correto
amitnair92
4
@ amitnair92 - Faça isso: r "C: \ Usuários \ Dell \ Desktop \ ProjectShadow \ button \ button.py" ou este: "C: \\ Usuários \\ Dell \\ Desktop \\ ProjectShadow \\ botão \\ botão .py "-" \ b "é um caractere especial (sistema 'sino', eu acho), semelhante a como \ r ou \ n significa nova linha / retorno de carro. Prefixar a string com r "C: \ ..." significa usar a entrada bruta fornecida
Bruce Lamond 31/01
87

Em python 3

>>> from pathlib import Path    
>>> Path("/tmp/d/a.dat").name
'a.dat'
Kishan B
fonte
3.4 a 3.6 ou posterior, dependendo exatamente de quais itens do pathlib você usa.
LightCC 25/09/19
8
Também é possível utilizar Path ( "some / path / to / file.dat")-tronco para obter o nome do arquivo sem a extensão do arquivo.
s2t2
47
import os
head, tail = os.path.split('path/to/file.exe')

cauda é o que você quer, o nome do arquivo.

Veja os documentos do módulo OS do python para obter detalhes

número 5
fonte
13
Apenas para que outros usuários sejam cuidadosos, isso retornará "" se os caminhos terminarem em "/" ou "\"
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
fonte
12

No seu exemplo, você também precisará remover a barra do lado direito para retornar 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 nível:

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

atualização: acho lazyrque forneceu a resposta certa. Meu código não funcionará com caminhos semelhantes a janelas em sistemas unix e vice-versa com caminhos semelhantes a unix em sistemas Windows.

Esqui
fonte
Sua resposta não funcionará r"a\b\c"no linux, nem "a/b/c"no windows.
Lauritz V. Thaulow
é claro, os.path.basename(path)só funcionará se os.path.isfile(path)for True. Portanto, path = 'a/b/c/'não é um nome de arquivo válido ...
moooeeeep
1
@fmaas os.path.basename é apenas uma função de processamento de string. Ele não se importa se o arquivo existe ou se é um arquivo ou dir. os.path.basename("a/b/c/")retorna ""devido à barra final.
Lauritz V. Thaulow 05/12/19
lazyrvocê está certo! Eu não pensei sobre isso. Seria seguro apenas fazer path = path.replace('\\', '/')?
Ski
@Skirmantas, suponho, mas não parece certo. Eu acho que o processamento do caminho deve ser feito com as ferramentas internas que foram feitas para o trabalho. Há muito mais nos caminhos do que aparenta.
Lauritz V. Thaulow
11
fname = str("C:\Windows\paint.exe").split('\\')[-1:][0]

isso retornará: paint.exe

altere o valor sep da função de divisão em relação ao seu caminho ou SO.

Eslam Hamouda
fonte
Essa é a resposta que eu gostei, mas por que não fazer o seguinte? fname = str(path).split('/')[-1]
asultan904 25/02
10

Se você deseja obter o nome do arquivo automaticamente, pode fazer

import glob

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

Se o caminho do arquivo não terminar com "/" e os diretórios separados por "/", use o código a seguir. Como sabemos, geralmente o caminho não termina com "/".

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

Mas, em alguns casos, como os URLs terminam com "/", use o seguinte código

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

mas quando seu caminho é marcado por "\", que você geralmente encontra nos caminhos do Windows, pode usar os seguintes 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]))

Você pode combinar as duas em uma função, verificando o tipo de SO e retornando o resultado.

Santosh kumar Manda
fonte
7

Isso funciona para linux e windows, bem como para a biblioteca padrão

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
fonte
6

Aqui está uma solução somente regex, que parece funcionar com qualquer caminho do sistema operacional em qualquer sistema operacional.

Nenhum outro módulo é necessário e também não é necessário pré-processamento:

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']

Atualizar:

Se você quiser apenas uma potencial filename, se presente (ou seja, /a/b/é um dir e assim é c:\windows\), altere o regex para: r'[^\\/]+(?![\\/])$'. Para o "regex contestado", isso altera a aparência positiva positiva de algum tipo de barra para uma negativa negativa, fazendo com que os nomes de caminho que terminam com a barra não retornem nada, em vez do último subdiretório do nome do caminho. Obviamente, não há garantia de que o nome do arquivo em potencial se refira a um arquivo e, para isso, os.path.is_dir()ou os.path.is_file()precise ser empregado.

Isso corresponderá da seguinte forma:

/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

O regex pode ser testado aqui .

Eric Duminil
fonte
você está usando re, por que não o módulo OS?
Saurabh Chandra Patel
@SaurabhChandraPatel faz muito tempo. Se bem me lembro, o regex é usado como uma solução de plataforma cruzada neste caso. Você pode processar nomes de arquivos do Windows em um servidor Linux, por exemplo.
Eric Duminil 15/04
5

Talvez apenas a minha solução completa sem importantes novidades (considere o tempfile para criar arquivos temporários: D)

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

Obter os valores de abc.nameserá uma sequência como esta: '/tmp/tmpks5oksk7' Para que eu possa substituir o /por um espaço .replace("/", " ")e depois chamar split(). Isso retornará uma lista e eu recebo o último elemento da lista com[-1]

Não é necessário importar nenhum módulo.

Akendo
fonte
2
E se o nome do arquivo ou um diretório contiver um espaço?
quer
1
Que tal uma divisão direta ("/") [- 1]?
Nan
4

Nunca vi caminhos com barra invertida dupla, eles existem? O recurso interno do módulo python osfalha para eles. Todos os outros funcionam, também a ressalva dada por você com 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
fonte
Esses não são retrolavados duplos. São barras invertidas simples e precisam ser escapadas.
Eric Duminil
3

O separador do Windows pode estar em um nome de arquivo Unix ou em Caminho do Windows. O separador Unix pode existir apenas no caminho Unix. A presença de um separador Unix indica um caminho que não é do Windows.

A seguir, o separador específico do sistema operacional será separado (separador de corte cortado), depois será dividido e retornará o valor mais à direita. É feio, mas simples, com base no pressuposto acima. Se a suposição estiver incorreta, atualize e eu atualizarei esta resposta para atender às condições mais precisas.

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

Código de amostra:

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
fonte
1
Além disso, sinta-se à vontade para me enviar sugestões sobre como formatar neste local. Foram necessárias meia dúzia de tentativas para colocar o código de exemplo no lugar.
Dusc2don 16/05
1

Para completar, aqui está a pathlibsolução para o 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']

Isso funciona no Windows e Linux.

Morgoth
fonte
1

No Python 2 e 3, usando o 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']

Com sua caixa de teste:

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

A idéia aqui é converter todos os caminhos na representação interna unificada de pathlib2, com diferentes decodificadores, dependendo da plataforma. Felizmente, pathlib2inclui um decodificador genérico chamado PurePathque deve funcionar em qualquer caminho. Caso isso não funcione, você pode forçar o reconhecimento do caminho do Windows usando fromwinpath=True. Isso dividirá a sequência de entrada em partes, a última é a folha que você está procurando, daí a path2unix(t)[-1].

Se o argumento nojoin=False, o caminho será juntado novamente, de forma que a saída seja simplesmente a sequência de entrada convertida para um formato Unix, que pode ser útil para comparar subcaminhos entre plataformas.

laborioso
fonte