Como dividir um caminho dos em seus componentes em Python


153

Eu tenho uma variável de seqüência de caracteres que representa um caminho dos, por exemplo:

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

Eu quero dividir esta string em:

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

Eu tentei usar split()e, replace()mas eles apenas processam a primeira barra invertida ou inserem números hexadecimais na string.

Eu preciso converter essa variável de string em uma string bruta de alguma forma, para que eu possa analisá-la.

Qual é a melhor forma de fazer isso?

Devo acrescentar também que o conteúdo do varcaminho que estou tentando analisar é realmente o valor de retorno de uma consulta de linha de comando. Não são os dados do caminho que eu me gero. Ele é armazenado em um arquivo e a ferramenta de linha de comando não vai escapar das barras invertidas.


6
Ao revisar essas respostas, lembre-se de que os.path.splitnão está funcionando para você, porque você não está escapando dessa string corretamente.
Jed Smith

Você precisa escapar da string ou usar a string não processada: r"d:\stuff\morestuff\furtherdown\THEFILE.txt"para impedir que coisas como \ssejam mal interpretadas.
smci

Respostas:


164

Já fui mordido várias vezes por pessoas que escreviam suas próprias funções de mexer no caminho e erravam. Espaços, barras, barras invertidas, dois pontos - as possibilidades de confusão não são infinitas, mas os erros são facilmente cometidos de qualquer maneira. Portanto, sou um defensor do uso os.pathe recomendo-o nessa base.

(No entanto, o caminho para a virtude não é o mais fácil de seguir, e muitas pessoas, ao descobrirem isso, são tentadas a seguir um caminho escorregadio direto para a condenação. Elas não perceberão até que um dia tudo desmoronar, e elas - ou , mais provavelmente, outra pessoa - precisa descobrir por que tudo deu errado, e alguém criou um nome de arquivo que mistura barras e barras invertidas - e algumas pessoas sugerem que a resposta é "não fazer isso". Com exceção de quem misturou barras e barras invertidas - você pode ser elas, se quiser.)

Você pode obter a unidade e o caminho + arquivo assim:

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

Obtenha o caminho e o arquivo:

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

Obter nomes de pastas individuais não é especialmente conveniente, mas é o tipo de desconforto mediano honesto que aumenta o prazer de encontrar algo que realmente funciona bem mais tarde:

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

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

        break

folders.reverse()

(Isso aparece "\"no início de foldersse o caminho era originalmente absoluto. Você pode perder um pouco de código se não quiser.)


@ brone - Prefiro usar esta solução do que ter que me preocupar em escapar da barra invertida. obrigado!
BeeBand 13/07/10

1
Ficaria feliz em provar que estou errado, mas me parece que a solução sugerida não funcionará se um caminho como este "C: \ usr \ rs0 \ my0 \ in111102.log" for usado (a menos que a entrada inicial seja uma sequência bruta )?
Shearichard #

1
Parece que isso não dividirá adequadamente um caminho se ele contiver apenas um diretório no OSX, como "/ path / to / my / folder /", para conseguir que você queira adicionar essas duas linhas ao início: if path.endswith("/"):e path = path[:-1].
Kevin London

1
Eu prefiro solução por @Tompa
jaycode 26/11

1
Concordo com o jaycode : a solução de Tompa é a abordagem canônica e deveria ter sido a resposta aceita. Essa alternativa excessivamente complexa, ineficiente e suscetível a erros falha na aprovação do código de produção. Não motivo razoável para tentar (... e falhar, é claro) analisar iterativamente nomes de caminhos quando a simples divisão de cadeias é bem-sucedida com apenas uma única linha de código.
22915 Cecil Curry

287

eu faria

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

Primeiro normalize a sequência do caminho em uma sequência adequada para o sistema operacional. Em seguida, os.sepdeve ser seguro usá-lo como um delimitador na divisão da função de sequência.


25
A única resposta verdadeira: surgiu . A solução canônica é a mais simples, é claro. Ver! Pois é elegante e contínuo e não possui capas de ar insuportáveis.
22915 Cecil Curry

20
Como um one-liner,os.path.normpath(a_path).split(os.path.sep)
Daniel Farrell

2
Isso não parece funcionar para path = root. Nesse caso, o resultado de path.split é ['', '']. De fato, em geral, essa solução split () fornece um diretório mais à esquerda com o nome da string vazia (que pode ser substituída pela barra apropriada). O principal problema é que uma única barra (para frente ou para trás, dependendo do sistema operacional) é o nome do diretório raiz, enquanto em outras partes do caminho é um separador .
gwideman

2
Será que vai funcionar melhor com um lstrip então? os.path.normpath(path).lstrip(os.path.sep).split(os.path.sep)
Vidar

1
@ user60561 Isso ocorre porque no Linux, a barra invertida é um caractere permitido nos nomes de arquivos, enquanto no Windows uma barra não é. É por isso que no Windows normpathreconhecerá a barra como um separador. No Linux, normpathbasta assumir que você tem um diretório chamado \1\2e um arquivo ou diretório dentro dele chamado 3.
Vojislav Stojkovic

81

Você pode simplesmente usar a abordagem mais Pythonic (IMHO):

import os

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

O que lhe dará:

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

A dica aqui é usar em os.sepvez de '\\'ou '/', pois isso o torna independente do sistema.

Para remover dois pontos da letra da unidade (embora eu não veja nenhuma razão para fazer isso), você pode escrever:

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

22
Isso funciona some times. Outras vezes (no Windows, pelo menos), você encontrará caminhos parecidos folder\folder2\folder3/file.txt. É melhor normalizar primeiro (os.path.normpath) o caminho e depois dividi-lo.
22614 vikki

7
Esta resposta estava quase lá. Como vikki sugere, a falha em normalizar os nomes de caminho antes que os feitiços de divisão de cordas sejam destruídos em casos comuns (por exemplo, /foo//bar). Veja a resposta da Tompa para uma solução mais robusta.
22415 Cecil Curry

62

No Python> = 3.4, isso se tornou muito mais simples. Agora você pode usar pathlib.Path.partspara obter todas as partes de um caminho.

Exemplo:

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

Em uma instalação do Python 3 no Windows, isso pressupõe que você esteja trabalhando com caminhos do Windows e, no * nix, assumirá que você está trabalhando com caminhos do posix. Isso geralmente é o que você deseja, mas se não for, pode usar as classes pathlib.PurePosixPathou pathlib.PureWindowsPathconforme necessário:

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

Edit: Há também um backport para python 2 disponível: pathlib2


3
Path.parts é o que eu sempre quis, mas nunca soube que existia até hoje.
30818 JamEnergy

por que isso não foi envolvido em uma boa função python nativa?
Eduardo Pignatelli

2
Essa é a resposta!
nayriz 8/03

11

O problema aqui começa com como você está criando a string em primeiro lugar.

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

Feito desta forma, Python está tentando caso especial estes: \s, \m, \f, e \T. No seu caso, \festá sendo tratado como um avanço de página (0x0C) enquanto as outras barras invertidas são tratadas corretamente. O que você precisa fazer é um destes:

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

Depois de dividir um desses, você obterá o resultado desejado.


@W. Craig Trader - obrigado, mas esse caminho não é o que eu me gero - ele volta para mim de outro programa e eu tenho que armazenar esses dados em uma variável. Não sei como converter dados armazenados em uma variável em "texto bruto".
BeeBand 13/07/10

Não existe um "texto bruto" ... é exatamente como você o representa na fonte. Anexe r "" à string ou passe-a através de .replace ('\\', '/')
Marco Mariani

@BeeBand, como você está recuperando os dados de outro programa? Você está lendo isso de um arquivo, um cano, um soquete? Se sim, então você não precisa fazer nada sofisticado; o único motivo para dobrar barras invertidas ou usar cadeias brutas é colocar constantes de cadeia no código Python. Por outro lado, se o outro programa estiver gerando barras duplas e invertidas, convém limpar isso antes de dividir seu caminho.
Craig Trader

@W. Craig Trader - estou lendo de um arquivo que é escrito por outro programa. Não consegui split()ou replace()trabalhei por algum motivo - continuei recebendo valores hexadecimais. Você está certo, porém, acho que estava latindo na árvore errada com a ideia de cadeia bruta - acho que estava usando split()incorretamente. Porque eu tentei algumas dessas soluções usando split()e elas funcionam para mim agora.
BeeBand

10

Para uma solução um pouco mais concisa, considere o seguinte:

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

Esta é a minha solução favorita para este problema. Muito agradável.
Will Moore

1
Isso não funciona se o caminho terminar com /. Além disso, fornece uma string vazia no início da lista, se o caminho começar com #/
Sorig 19/10/16

4

Na verdade, não posso contribuir com uma resposta real para essa (como eu vim aqui esperando encontrar uma), mas para mim o número de abordagens diferentes e todas as advertências mencionadas é o indicador mais seguro de que o módulo os.path do Python precisa desesperadamente disso. como uma função interna.


4

A maneira funcional, com um gerador .

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

Em ação:

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

3

Funciona para mim:

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

Claro, talvez você precise remover o cólon do primeiro componente, mas mantê-lo possibilita a remontagem do caminho.

O rmodificador marca a string literal como "bruta"; observe como as barras invertidas incorporadas não são duplicadas.


@unwind - rna frente da sua string, a que isso se refere?
BeeBand

2
r significa string não processada - escapa automaticamente os \ caracteres. É útil usar sempre que você estiver fazendo caminhos.
Wayne Werner

1
@ BeeBand: você não precisa se importar; er "" é apenas algo que importa durante a compilação / análise do código, não é algo que se torna uma propriedade da string depois de analisada. Significa apenas "aqui está uma string literal, mas não interprete barras invertidas como tendo outro significado que não sejam barras invertidas".
descontrair

3
Eu acho que pode ser útil mencioná-lo menos bem, faça-o mais ambíguo usando a.split (os.sep) em vez de codificá-lo?
Tim McJilton

4
Eu tenho que recusar a votação por perder uma chance de explicar os.path.splite os.pathsep, considerando que ambos são muito mais portáteis do que o que você escreveu. Pode não ser importante para o OP agora, mas será quando ele estiver escrevendo algo que precisa mover plataformas.
Jed Smith

3

O material sobre about mypath.split("\\")seria melhor expresso como mypath.split(os.sep). sepé o separador de caminho para sua plataforma específica (por exemplo, \para Windows, /Unix etc.), e a compilação Python sabe qual usar. Se você usar sep, seu código será independente de plataforma.


1
Or os.path.split. Você quer ter cuidado os.pathsep, porque está :na minha versão do Python no OS X (e os.path.splitlida corretamente /).
precisa

4
Você quer dizer que os.sepnão os.pathsep. Siga a sabedoria dos os.sepdocumentos: Observe que saber isso não é suficiente para poder analisar ou concatenar nomes de caminho - use os.path.split () e os.path.join ().
21812 Jon-Eric

1

re.split () pode ajudar um pouco mais do que string.split ()

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

Se você também deseja oferecer suporte a caminhos Linux e Mac, basta adicionar filtro (Nenhum, resultado), para remover os '' indesejados da divisão (), pois os caminhos começam com '/' ou '//'. por exemplo '// mount / ...' ou '/ 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

Você pode recursivamente os.path.splita string

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

Testando isso em algumas cadeias de caminho e remontando o caminho com 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

O primeiro elemento da lista pode precisar ser tratado de maneira diferente, dependendo de como você deseja lidar com letras de unidade, caminhos UNC e caminhos absolutos e relativos. Alterar o último [p]para [os.path.splitdrive(p)]força o problema dividindo a letra da unidade e o diretório raiz em uma 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']

Edit: Eu percebi que esta resposta é muito semelhante à dada acima pelo user1556435 . Estou deixando minha resposta, pois a manipulação do componente da unidade do caminho é diferente.


0

Assim como outros explicaram - o problema foi causado pelo uso \, que é um caractere de escape na string literal / constante. OTOH, se você tivesse a sequência do caminho do arquivo de outra fonte (lida do arquivo, console ou retornada pela função os) - não haveria problemas de divisão em '\\' ou r '\'.

E, assim como outros sugeriram, se você quiser usar \no literal programa, você tem que quer duplicá-lo \\ou todo o literal tem de ser precedido por r, como assim r'lite\ral'ou r"lite\ral"para evitar o analisador converter esse \e rpara CR caracteres (retorno de carro).

Porém, há mais uma maneira - apenas não use \nomes de caminho de barra invertida no seu código! Desde o século passado, o Windows reconhece e funciona bem com nomes de caminho que usam barra como separador de diretório /! De alguma forma, muitas pessoas não sabem disso .. mas funciona:

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

A propósito, isso fará com que seu código funcione no Unix, Windows e Mac ... porque todos eles usam /como separador de diretório ... mesmo que você não queira usar as constantes predefinidas do módulo os.


Infelizmente, os dados estão sendo retornados para mim a partir de outro programa que eu executo no meu script python. Não tenho controle sobre o uso de '\' ou '/' - é o programa de terceiros que determina isso (provavelmente em uma plataforma).
BeeBand 13/07/10

@BeeBand: Ah, então você não terá o problema que teve durante o teste, quando forneceu a string como literal em seu programa. Ou você pode fazer o seguinte truque mal após receber o caminho: var = var.replace('\\','/')- substitua \ por / e continue trabalhando apenas com barras :) :)
Nas Banov

que é de fato um corte mal: o)
BeeBand

@ BeeBand: é por isso que eu avisei. Quando digo que algo é mau, não quero dizer necessariamente que nunca deva ser usado - mas é preciso estar muito ciente do motivo pelo qual está usando e alerta para consequências não intencionais. Neste caso, uma consequência muito improvável é que, se isso é usado no sistema de arquivos Unix com `` uso no nome do arquivo ou diretório (é realmente difícil, mas possível) - Este código irá 'quebrar'
Nas Banov

0

Vamos supor que você tenha um arquivo filedata.txtcom conteúdo:

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

Você pode ler e dividir os caminhos do arquivo:

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

isso realmente funciona, obrigado! Mas escolhi a solução da brone porque prefiro não me preocupar em escapar da barra invertida.
BeeBand

9
Não é pitônico, pois depende do sistema de arquivos.
jb.

0

Eu uso o seguinte, pois, como ele usa a função os.path.basename, ele não adiciona nenhuma barra à lista retornada. Também funciona com as barras de qualquer plataforma: por exemplo, \\ da janela ou / do unix. Além disso, ele não adiciona o \\\\ que o Windows usa para os caminhos do 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

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

você recebe

['servidor', 'pasta1', 'pasta2', 'pasta3', 'pasta4']


1
Isso não segue o invariável para o qual passar o resultado os.path.join()deve retornar a string original. Eu diria que a saída correta para o seu exemplo de entrada é [r'\\','server','folder1','folder2','folder3','folder4']. Ou seja, o que os.path.split()faz.
21812 Jon-Eric

0

Na verdade, não tenho certeza se isso responde totalmente à pergunta, mas me diverti escrevendo essa pequena função que mantém uma pilha, adere às manipulações baseadas no os.path e retorna a lista / pilha de itens.

  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

A linha de código abaixo pode lidar com:

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

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


0

Um recursivo para a diversão.

Não é a resposta mais elegante, mas deve funcionar em qualquer lugar:

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 fato, desculpe. Deveria ter lido atentamente a pergunta ... um caminho para 'dos'.
DuGNu 16/01

-1

usar ntpath.split()


quando eu uso os.path.split () recebo, ( d:\\stuff, morestuff\x0curtherdown\thefile.mux)
BeeBand

Como BeeBand apontou, os.path.split () realmente não faz a coisa desejada.
descontraia

desculpe, eu acabei de perceber que os.path só funciona dependendo do seu sistema operacional. O ntpath irá analisar os caminhos.
Deft_code

mesmo com ntpath eu ainda recebod:\\stuff, morestuff\x0curtherdown\thefile.mux
BeeBand

2
@ BeeBand: você está tendo problemas com o escape de sua string. '\x0c'é o caractere de feed de formulário. A maneira de criar o caractere de feed de formulário é '\ f'. Se você realmente deseja a string literal '\ f', você tem duas opções: '\\f'ou r'\f'.
Deft_code
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.