Comment diviser un chemin DOS en ses composants en Python

154

J'ai une variable de chaîne qui représente un chemin dos, par exemple:

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

Je veux diviser cette chaîne en:

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

J'ai essayé d'utiliser split()et replace()mais ils ne traitent que la première barre oblique inverse ou ils insèrent des nombres hexadécimaux dans la chaîne.

J'ai besoin de convertir cette variable de chaîne en une chaîne brute afin de pouvoir l'analyser.

Quelle est la meilleure façon de procéder?

Je devrais également ajouter que le contenu de varie le chemin que j'essaie d'analyser, est en fait la valeur de retour d'une requête de ligne de commande. Ce ne sont pas des données de chemin que je génère moi-même. Il est stocké dans un fichier et l'outil de ligne de commande n'échappera pas aux barres obliques inverses.

BeeBand
la source
6
Lorsque vous examinez ces réponses, n'oubliez pas que cela os.path.splitne fonctionne pas pour vous car vous n'échappez pas correctement à cette chaîne.
Jed Smith du
Vous devez échapper à la chaîne ou utiliser rawstring: r"d:\stuff\morestuff\furtherdown\THEFILE.txt"pour éviter que des choses comme \sune mauvaise interprétation.
smci le

Réponses:

165

J'ai été mordu de nombreuses fois par des gens qui écrivent leurs propres fonctions de violon et se trompent. Espaces, barres obliques, barres obliques inverses, deux-points - les possibilités de confusion ne sont pas infinies, mais des erreurs sont faciles de toute façon. Je suis donc très perspicace pour l'utilisation de os.path, et je le recommande sur cette base.

(Cependant, le chemin de la vertu n'est pas celui qui est le plus facile à emprunter, et beaucoup de gens, lorsqu'ils le trouvent, sont tentés de prendre un chemin glissant directement vers la damnation. Ils ne réaliseront pas avant qu'un jour tout tombe en morceaux, et ils - ou , plus probablement, quelqu'un d'autre - doit trouver pourquoi tout a mal tourné, et il s'avère que quelqu'un a créé un nom de fichier qui mélange des barres obliques inverses et des barres obliques inverses - et quelqu'un suggère que la réponse est "ne pas faire cela". Don ' t être l'une de ces personnes. À l'exception de celle qui a mélangé des barres obliques inverses et des barres obliques inverses - vous pourriez être elles si vous le souhaitez.)

Vous pouvez obtenir le lecteur et le chemin + le fichier comme ceci:

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

Récupérez le chemin et le fichier:

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

Obtenir les noms de dossiers individuels n'est pas particulièrement pratique, mais c'est le genre d'inconfort moyen honnête qui augmente le plaisir de trouver plus tard quelque chose qui fonctionne bien:

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

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

        break

folders.reverse()

(Cela apparaît "\"au début de folderssi le chemin était à l'origine absolu. Vous pourriez perdre un peu de code si vous ne le vouliez pas.)

HunnyBear
la source
@brone - Je préfère utiliser cette solution, plutôt que d'avoir à me soucier d'échapper à la barre oblique inverse. Merci!
BeeBand
1
Je serais heureux d'avoir tort mais il me semble que la solution suggérée ne fonctionne pas si un chemin comme celui-ci "C: \ usr \ rs0 \ my0 \ in111102.log" est utilisé (à moins que l'entrée initiale soit une chaîne brute )?
shearichard
1
Il semble que cela ne divisera pas correctement un chemin s'il ne contient qu'un répertoire dans OSX tel que "/ chemin / vers / mon / dossier /", pour y parvenir, vous voudrez ajouter ces deux lignes au début: if path.endswith("/"):et path = path[:-1].
Kevin Londres
1
Je préfère la solution de @Tompa
jaycode
1
Je suis d'accord avec jaycode : la solution de Tompa est l' approche canonique et aurait dû être la réponse acceptée. Cette alternative trop complexe, inefficace et sujette aux erreurs ne parvient pas à transmettre le code de production. Il n'y a aucune raison raisonnable d'essayer (... et d'échouer, bien sûr) d'analyser de manière itérative les noms de chemin lorsque le fractionnement de chaînes simple réussit avec une seule ligne de code.
Cecil Curry
287

je ferais

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

Commencez par normaliser la chaîne de chemin en une chaîne appropriée pour le système d'exploitation. Ensuite, il os.sepdoit être sûr de l'utiliser comme délimiteur dans la fonction de chaîne fractionnée.

Tompa
la source
25
La seule vraie réponse: elle a émergé . La solution canonique est bien entendu la plus simple. Voir! Car il est élégant et discret et n'a pas de bordures insupportables.
Cecil Curry du
20
En tant que one-liner,os.path.normpath(a_path).split(os.path.sep)
Daniel Farrell
2
Cela ne semble pas fonctionner pour path = root. Dans ce cas, le résultat de path.split est ['', '']. En fait, en général, cette solution split () donne un répertoire le plus à gauche avec un nom de chaîne vide (qui pourrait être remplacé par la barre oblique appropriée). Le problème principal est qu'une seule barre oblique (en avant ou en arrière selon le système d'exploitation) est le nom du répertoire racine, alors qu'ailleurs dans le chemin, c'est un séparateur .
gwideman
2
Cela fonctionnera-t-il mieux avec un ruban adhésif alors? os.path.normpath(path).lstrip(os.path.sep).split(os.path.sep)
Vidar
1
@ user60561 C'est parce que sous Linux, la barre oblique inverse est un caractère autorisé dans les noms de fichiers, alors que sous Windows, une barre oblique ne l'est pas. C'est pourquoi sur Windows, normpathreconnaîtra la barre oblique comme séparateur. Sous Linux, normpathsupposera simplement que vous avez un répertoire appelé \1\2et un fichier ou un répertoire à l'intérieur appelé 3.
Vojislav Stojkovic
81

Vous pouvez simplement utiliser l'approche la plus pythonique (IMHO):

import os

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

Ce qui vous donnera:

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

L'indice ici est d'utiliser à la os.sepplace de '\\'ou '/', car cela le rend indépendant du système.

Pour supprimer deux points de la lettre de lecteur (bien que je ne vois aucune raison pour laquelle vous voudriez le faire), vous pouvez écrire:

path_list[0] = path_list[0][0]
Maciek D.
la source
22
Cela fonctionne some times. D'autres fois (sur Windows au moins), vous trouverez des chemins qui ressemblent à folder\folder2\folder3/file.txt. Il est préférable de normaliser d'abord (os.path.normpath) le chemin, puis de le diviser.
vikki
7
Cette réponse était presque là. Comme le suggère vikki , l'échec de normalisation des chemins avant la division des chaînes est une perte pour les cas extrêmes courants (par exemple, /foo//bar). Voir la réponse de Tompa pour une solution plus robuste.
Cecil Curry du
62

En Python> = 3.4, cela est devenu beaucoup plus simple. Vous pouvez maintenant utiliserpathlib.Path.parts pour obtenir toutes les parties d'un chemin.

Exemple:

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

Sur une installation Windows de Python 3, cela supposera que vous travaillez avec des chemins Windows, et sur * nix, cela supposera que vous travaillez avec des chemins posix. C'est généralement ce que vous voulez, mais si ce n'est pas le cas, vous pouvez utiliser les classes pathlib.PurePosixPathou pathlib.PureWindowsPathselon vos besoins:

>>> 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: Il existe également un backport vers python 2 disponible: pathlib2

freidrichen
la source
3
Path.parts est ce que j'ai toujours voulu, mais je n'ai jamais su qu'il existait jusqu'à aujourd'hui.
JamEnergy
pourquoi cela n'a-t-il pas été enveloppé d'une belle fonction python native?
Eduardo Pignatelli
2
TELLE est la réponse!
nayriz
11

Le problème ici commence par la façon dont vous créez la chaîne en premier lieu.

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

Fait de cette façon, Python essaie de cas particulier celles - ci: \s, \m, \fet \T. Dans votre cas, \fest traité comme un saut de page (0x0C) tandis que les autres barres obliques inverses sont gérées correctement. Voici ce que vous devez faire:

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

Ensuite, une fois que vous avez divisé l'un ou l'autre, vous obtiendrez le résultat souhaité.

Craig Trader
la source
@W. Craig Trader - merci, mais ce chemin n'est pas celui que je génère moi-même - il me revient d'un autre programme et je dois stocker ces données dans une variable. Je ne sais pas comment convertir les données stockées dans une variable en «texte brut».
BeeBand
Il n'y a pas de "texte brut" ... c'est juste comment vous le représentez dans la source. Soit ajouter r "" à la chaîne, soit le passer par .replace ('\\', '/')
Marco Mariani
@BeeBand, comment récupérez-vous les données de l'autre programme? Le lisez-vous à partir d'un fichier, d'un tube, d'une socket? Si c'est le cas, vous n'avez pas besoin de faire quelque chose d'extraordinaire; la seule raison de doubler les barres obliques inverses ou d'utiliser des chaînes brutes est de placer des constantes de chaîne dans le code Python. D'un autre côté, si l'autre programme génère des doubles contre-obliques, vous voudrez le nettoyer avant de diviser votre chemin.
Craig Trader
@W. Craig Trader - Je le lis à partir d'un fichier, qui est écrit par un autre programme. Je ne pouvais pas obtenir split()ou replace()travailler pour une raison quelconque - j'ai continué à obtenir des valeurs hexadécimales. Vous avez raison cependant, je pense que j'aboyais le mauvais arbre avec l'idée de chaîne brute - je pense que je n'utilisais simplement split()pas correctement. Parce que j'ai essayé certaines de ces solutions en utilisant split()et elles fonctionnent pour moi maintenant.
BeeBand
10

Pour une solution un peu plus concise, considérez ce qui suit:

def split_path(p):
    a,b = os.path.split(p)
    return (split_path(a) if len(a) and len(b) else []) + [b]
user1556435
la source
C'est ma solution préférée à ce problème. Très agréable.
Will Moore
1
Cela ne fonctionne pas si le chemin se termine par /. En outre, vous donne une chaîne vide au début de la liste si votre chemin commence par/
Sorig
4

Je ne peux pas réellement apporter une vraie réponse à celle-ci (car je suis venu ici en espérant en trouver une moi-même), mais pour moi, le nombre d'approches différentes et toutes les mises en garde mentionnées est l'indicateur le plus sûr que le module os.path de Python en a désespérément besoin. comme fonction intégrée.

antred
la source
4

La manière fonctionnelle, avec un générateur .

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

En action:

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

Ça marche pour moi:

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

Bien sûr, vous devrez peut-être également supprimer les deux points du premier composant, mais le garder permet de réassembler le chemin.

Le rmodificateur marque la chaîne littérale comme "brute"; remarquez que les contre-obliques intégrées ne sont pas doublées.

se détendre
la source
@unwind - rdevant votre chaîne, à quoi cela fait-il référence?
BeeBand
2
r signifie chaîne brute - il échappe automatiquement les \ caractères. Il est utile d'utiliser chaque fois que vous faites des chemins.
Wayne Werner
1
@BeeBand: vous n'avez pas besoin de vous en soucier; le r "" est juste quelque chose qui compte lors de la compilation / analyse du code, ce n'est pas quelque chose qui devient une propriété de la chaîne une fois analysée. Cela signifie simplement "voici une chaîne littérale, mais n'interprétez pas les contre-obliques comme ayant une autre signification que des contre-obliques".
détendre le
3
Je pense qu'il pourrait être utile de vous mentionner moins bien le faire plus ambigu en utilisant a.split (os.sep) au lieu de le coder en dur?
Tim McJilton
4
Je dois vous rejeter pour avoir manqué une chance d'expliquer os.path.splitet os.pathsep, étant donné que ces deux éléments sont beaucoup plus portables que ce que vous avez écrit. Cela n'a peut-être pas d'importance pour OP maintenant, mais ce sera le cas quand il écrit quelque chose qui doit déplacer les plates-formes.
Jed Smith
3

Le truc à propos de mypath.split("\\")serait mieux exprimé comme mypath.split(os.sep). sepest le séparateur de chemin pour votre plate-forme particulière (par exemple, \pour Windows, /pour Unix, etc.), et la construction Python sait laquelle utiliser. Si vous utilisez sep, votre code sera indépendant de la plate-forme.

Chris
la source
1
Ou os.path.split. Vous voulez faire attention os.pathsep, car c'est :sur ma version de Python sous OS X (et os.path.splitgère correctement /).
Jed Smith
4
Tu veux dire os.sepnon os.pathsep. Suivez la sagesse de la os.sepdocumentation: notez que cela ne suffit pas pour pouvoir analyser ou concaténer les chemins d'accès - utilisez os.path.split () et os.path.join ().
Jon-Eric
1

re.split () peut aider un peu plus que string.split ()

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

Si vous souhaitez également prendre en charge les chemins Linux et Mac, ajoutez simplement un filtre (Aucun, résultat), afin de supprimer les "" indésirables de split () puisque leurs chemins commencent par "/" ou "//". par exemple '// 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']
Asi
la source
1

Vous pouvez récursivement os.path.splitla chaîne

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

Tester cela par rapport à certaines chaînes de chemin et réassembler le chemin avec 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

Le premier élément de la liste peut devoir être traité différemment selon la manière dont vous souhaitez traiter les lettres de lecteur, les chemins UNC et les chemins absolus et relatifs. Changer le dernier [p]pour [os.path.splitdrive(p)]forcer le problème en divisant la lettre de lecteur et la racine du répertoire en un tuple.

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: J'ai réalisé que cette réponse est très similaire à celle donnée ci-dessus par user1556435 . Je laisse ma réponse car la gestion du composant d'entraînement du chemin est différente.

Mike Robins
la source
0

Tout comme d'autres l'ont expliqué - votre problème provenait de l'utilisation \, qui est un caractère d'échappement dans une chaîne littérale / constante. OTOH, si vous aviez cette chaîne de chemin de fichier d'une autre source (lue à partir du fichier, de la console ou renvoyée par la fonction os) - il n'y aurait pas eu de problème de division sur '\\' ou r '\'.

Et tout comme d'autres l'ont suggéré, si vous voulez utiliser \dans le littéral du programme, vous devez soit le dupliquer, \\soit tout le littéral doit être préfixé par r, comme ça r'lite\ral'ou r"lite\ral"pour éviter que l'analyseur ne convertisse cela \etr en caractère CR (retour chariot).

Il existe cependant un autre moyen: n'utilisez simplement pas de \noms de chemin de barre oblique inverse dans votre code! Depuis le siècle dernier, Windows reconnaît et fonctionne très bien avec les chemins d'accès qui utilisent la barre oblique comme séparateur de répertoire /! Peu de gens le savent, mais cela fonctionne:

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

Cela permettra à votre code de fonctionner sous Unix, Windows et Mac ... car ils l'utilisent tous /comme séparateur de répertoire ... même si vous ne voulez pas utiliser les constantes prédéfinies du moduleos .

Nas Banov
la source
Malheureusement, les données me sont renvoyées à partir d'un autre programme que j'exécute à partir de mon script python. Je n'ai aucun contrôle sur l'utilisation de '\' ou '/' - c'est le programme tiers qui détermine cela (probablement sur la base d'une plate-forme).
BeeBand
@BeeBand: Ah, alors vous n'aurez pas le problème que vous avez rencontré pendant les tests, lorsque vous avez fourni la chaîne comme littérale dans votre programme. Ou vous pouvez faire le hack diabolique suivant après avoir reçu le chemin: var = var.replace('\\','/')- remplacez \ par / et continuez à travailler avec des barres obliques uniquement :)
Nas Banov
c'est en effet un hack maléfique: o)
BeeBand
@BeeBand: c'est pourquoi j'ai prévenu. Quand je dis que quelque chose est mauvais, je ne veux pas nécessairement dire qu'il ne devrait jamais être utilisé - mais il faut bien savoir pourquoi ils l'utilisent et être alerté des conséquences involontaires. Dans ce cas, une conséquence très improbable est que si cela est utilisé sur le système de fichiers Unix avec `` utiliser dans le nom de fichier ou de répertoire (c'est vraiment difficile mais possible) - ce code va 'casser' '
Nas Banov
0

Supposons que vous ayez un fichier filedata.txtavec du contenu:

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

Vous pouvez lire et diviser les chemins de fichiers:

>>> for i in open("filedata.txt").readlines():
...     print i.strip().split("\\")
... 
['d:', 'stuff', 'morestuff', 'furtherdown', 'THEFILE.txt']
['d:', 'otherstuff', 'something', 'otherfile.txt']
zoli2k
la source
cela fonctionne en effet, merci! Mais j'ai choisi la solution de brone parce que je préfère ne pas me soucier d'échapper à la barre oblique inverse.
BeeBand
9
Pas pythonique car il dépend du système de fichiers.
jb.
0

J'utilise ce qui suit car puisqu'il utilise la fonction os.path.basename, il n'ajoute aucune barre oblique à la liste renvoyée. Il fonctionne également avec les barres obliques de n'importe quelle plate-forme: c'est-à-dire \\ de fenêtre ou / d'Unix. Et de plus, il n'ajoute pas le \\\\ que Windows utilise pour les chemins du serveur :)

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

Donc pour '\\\\ server \\ folder1 \\ folder2 \\ folder3 \\ folder4'

vous obtenez

['serveur', 'dossier1', 'dossier2', 'dossier3', 'dossier4']

Geai
la source
1
Cela ne suit pas l'invariant auquel passer votre résultat os.path.join()devrait renvoyer la chaîne d'origine. Je dirais que la sortie correcte pour votre exemple d'entrée est [r'\\','server','folder1','folder2','folder3','folder4']. Ie que os.path.split()fait.
Jon-Eric
0

Je ne suis pas vraiment sûr que cela réponde entièrement à la question, mais je me suis amusé à écrire cette petite fonction qui garde une pile, s'en tient aux manipulations basées sur os.path et renvoie la liste / pile d'éléments.

  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
la source
0

La ligne de code ci-dessous peut gérer:

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

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

Gour Bera
la source
0

Un récursif pour le plaisir.

Pas la réponse la plus élégante, mais devrait fonctionner partout:

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
la source
en effet, désolé. J'aurais dû lire attentivement la question ... un chemin «dos».
DuGNu
-1

utilisation ntpath.split()

deft_code
la source
quand j'utilise os.path.split () je reçois, ( d:\\stuff, morestuff\x0curtherdown\thefile.mux)
BeeBand
Comme l'a souligné BeeBand, os.path.split () ne fait vraiment pas la chose souhaitée.
détendre le
désolé, je viens de réaliser que os.path ne fonctionne que selon votre système d'exploitation. ntpath analysera les chemins dos.
deft_code
même avec ntpath, je reçois toujoursd:\\stuff, morestuff\x0curtherdown\thefile.mux
BeeBand
2
@BeeBand: vous rencontrez des problèmes pour échapper à votre chaîne. '\x0c'est le caractère d'alimentation de formulaire. La manière de créer le caractère de saut de formulaire est «\ f». Si vous voulez vraiment la chaîne littérale '\ f', vous avez deux options: '\\f'ou r'\f'.
deft_code