Wyodrębnij nazwę pliku ze ścieżki, bez względu na format os / path


794

Jakiej biblioteki Python mogę użyć do wyodrębnienia nazw plików ze ścieżek, bez względu na system operacyjny lub format ścieżki?

Na przykład chciałbym, aby wszystkie te ścieżki mi zwróciły 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

Odpowiedzi:


781

Używanie os.path.splitlub, os.path.basenamejak sugerują inni, nie będzie działać we wszystkich przypadkach: jeśli uruchomisz skrypt w systemie Linux i spróbujesz przetworzyć klasyczną ścieżkę w stylu systemu Windows, zakończy się niepowodzeniem.

Ścieżki systemu Windows mogą używać ukośnika odwrotnego lub ukośnika jako separatora ścieżek. Dlatego ntpathmoduł (który jest równoważny os.path podczas uruchamiania w systemie Windows) będzie działał dla wszystkich (1) ścieżek na wszystkich platformach.

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

Oczywiście, jeśli plik kończy się ukośnikiem, basename będzie pusty, więc stwórz własną funkcję, aby sobie z tym poradzić:

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

Weryfikacja:

>>> 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) Istnieje jedno zastrzeżenie: nazwy plików w systemie Linux mogą zawierać odwrotne ukośniki . Tak więc w systemie Linux r'a/b\c'zawsze odnosi się do pliku b\cw afolderze, podczas gdy w systemie Windows zawsze odnosi się do cpliku w bpodfolderze afolderu. Jeśli więc na ścieżce używane są zarówno ukośniki do przodu, jak i do tyłu, musisz znać powiązaną platformę, aby móc ją poprawnie interpretować. W praktyce zazwyczaj bezpiecznie jest założyć, że jest to ścieżka systemu Windows, ponieważ odwrotne ukośniki są rzadko używane w nazwach plików Linuksa, ale pamiętaj o tym podczas pisania kodu, aby nie stworzyć przypadkowych luk bezpieczeństwa.


29
w systemie Windows os.pathpo prostu ładuje ntpathmoduł wewnętrznie. Za pomocą tego modułu można obsługiwać '\\'separatory ścieżek nawet na maszynach z systemem Linux. W Linuksie posixpathmoduł (odpowiednio os.path) uprości operacje na ścieżce, pozwalając tylko na '/'separatory w stylu posix .
moooeeeep,

@moooeeeep Więc moglibyśmy skorzystać z odpowiedzi Stranaca i czy jest ona wiarygodna? ( „Używanie os.path.split lub os.path.basename, jak sugerują inni, nie będzie działać we wszystkich przypadkach: jeśli uruchamiasz skrypt w systemie Linux i próbujesz przetworzyć klasyczną ścieżkę w stylu systemu Windows, to się nie powiedzie” - - cytat pochodzi z postu Lauritza - i nie rozumiem, czy to ostrzeżenie dotyczy odpowiedzi Stranaca, czy nie).
John CJ

3
@ johnc.j. Tylko wtedy, gdy trzeba przeanalizować ścieżki stylu Windows (np. r'C:\path\to\file.txt') Na komputerze z systemem Linux, należy użyć modułu ntpath. W przeciwnym razie możesz użyć funkcji z os.path. Wynika to z faktu, że systemy Linux zwykle dopuszczają stosowanie znaków odwrotnego ukośnika w nazwach plików (jak wyjaśniono w odpowiedzi).
moooeeeep,

2
Czy twoje rozwiązanie nie jest równoważne os.path.basename(os.path.normpath(path))?
Mr_and_Mrs_D

2
Dla tego, co warto przyszłym użytkownikom tego pytania, natknąłem się na sytuację, o której ostrzegał Lauritz, a jego rozwiązanie było jedyne, które zadziałało. Żadne finanglingowanie w systemie operacyjnym OS nie mogłoby wygenerować samej nazwy pliku. Więc imho, ntpath jest właściwą drogą.
Harabeck

1250

W rzeczywistości istnieje funkcja, która zwraca dokładnie to, czego chcesz

import os
print(os.path.basename(your_path))

22
Jeśli chcesz przetwarzać ścieżki w sposób niezależny od systemu operacyjnego, to dla os.path.basename (u "C: \\ temp \\ bla.txt") oczekujesz, że otrzymasz bla.txt. Pytanie nie dotyczy uzyskania poprawnej nazwy pliku, ale wyodrębnienia nazwy ścieżki.
Adi Roiban

3
Przy wyszukiwaniu w Google znalezienia nazwy ścieżki ścieżka ta odpowiedź była najbardziej pomocna. Mój przypadek użycia jest zresztą tylko w systemie Windows.
Bobort

2
os.path.basename(your_path)To zadziałało! Chciałem ścieżkę skryptu: os.path.dirname(os.path.realpath(__file__))i nazwę skryptu: os.path.basename(os.path.realpath(__file__)). Dzięki!
TheWalkingData

@AdiRoiban Czy mógłbyś rozwinąć swój komentarz? Przetestowałem to na Windows 7 i faktycznie otrzymuję „bla.txt”. Mówiąc wprost, nie widzę żadnego problemu (dla siebie).
John CJ

10
@ johnc.j. Chodzi o to, że kiedy próbowałeś tego w Linuksie, 'C:\\temp\\bla.txt'zamiast tego dostałeś .
moooeeeep,

218

os.path.split to funkcja, której szukasz

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

>>> print(tail)
a.dat
>>> print(head)
/tmp/d

40
Tylko dla innych użytkowników ostrożność zwraca „”, jeśli ścieżki kończą się na „/” lub „\”
BuZz

Kiedy próbuję "C: \ Users \ Dell \ Pulpit \ ProjectShadow \ \ button.py przycisk" zwraca jes "ProjectShadow Utton tton" do wszystkiego innego niż ten zwróci prawidłowy wynik
amitnair92

4
@ amitnair92 - Albo wykonaj to: r „C: \ Users \ Dell \ Desktop \ ProjectShadow \ button \ button.py” lub to: „C: \\ Users \\ Dell \\ Desktop \\ ProjectShadow \\ button \\ przycisk .py "-" \ b "jest znakiem specjalnym (myślę, że dzwonek systemowy), podobnie jak \ r lub \ n oznacza powrót do nowej linii / karetki. Prefiks ciągu r „C: \ ...” oznacza użycie podanego surowego wejścia
Bruce Lamond

87

W python 3

>>> from pathlib import Path    
>>> Path("/tmp/d/a.dat").name
'a.dat'

3.4 do 3.6 lub nowszy, w zależności dokładnie od używanych elementów ścieżki.
LightCC

8
może również użyć ścieżki („some / path / to / file.dat”). stem, aby uzyskać nazwę pliku bez rozszerzenia pliku
s2t2

47
import os
head, tail = os.path.split('path/to/file.exe')

ogon jest tym, czego chcesz, nazwą pliku.

Zobacz docs moduł Pythona os o szczegóły


13
Tylko dla innych użytkowników ostrożność zwraca „”, jeśli ścieżki kończą się na „/” lub „\”
BuZz

18
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

12

W twoim przykładzie musisz również usunąć ukośnik z prawej strony, aby zwrócić c:

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

Drugi poziom:

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

aktualizacja: Myślę, że lazyrdostarczyłem prawidłowej odpowiedzi. Mój kod nie będzie działać ze ścieżkami podobnymi do Windowsa w systemach uniksowych i odwrotnie w porównaniu ze ścieżkami podobnymi do Windowsa w systemie Windows.


Twoja odpowiedź nie będzie działać r"a\b\c"w systemie Linux, ani "a/b/c"w systemie Windows.
Lauritz V. Thaulow,

oczywiście os.path.basename(path)zadziała tylko wtedy, gdy os.path.isfile(path)jest True. Dlatego w path = 'a/b/c/'ogóle nie jest prawidłową nazwą pliku ...
moooeeeep

1
@fmaas os.path.basename jest wyłącznie funkcją przetwarzania łańcucha. Nie ma znaczenia, czy plik istnieje, czy to plik, czy katalog. os.path.basename("a/b/c/")zwraca z ""powodu ukośnika końcowego.
Lauritz V. Thaulow,

lazyrmasz rację! Nie myślałem o tym. Czy byłoby to bezpieczne path = path.replace('\\', '/')?
Ski

@ Skirmantas Przypuszczam, ale to nie wydaje się właściwe. Myślę, że przetwarzanie ścieżki powinno odbywać się za pomocą wbudowanych narzędzi, które zostały stworzone do tego zadania. Ścieżki mają znacznie więcej niż na pierwszy rzut oka.
Lauritz V. Thaulow,

11
fname = str("C:\Windows\paint.exe").split('\\')[-1:][0]

to zwróci: paint.exe

zmień wartość sep funkcji split w odniesieniu do ścieżki lub systemu operacyjnego.


To jest odpowiedź, która mi się podobała, ale dlaczego nie po prostu wykonać następujących czynności? fname = str(path).split('/')[-1]
asultan904

10

Jeśli chcesz automatycznie uzyskać nazwę pliku, możesz to zrobić

import glob

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

8

Jeśli ścieżka do pliku nie kończy się na „/”, a katalogi oddzielone „/”, użyj następującego kodu. Jak wiemy, ścieżka nie kończy się na „/”.

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

Ale w niektórych przypadkach, na przykład adresy URL kończą się na „/”, a następnie użyj następującego kodu

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

ale gdy Twoja ścieżka jest zapisywana jako „\”, co zwykle znajduje się w ścieżkach systemu Windows, możesz użyć następujących kodów

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

Możesz połączyć obie funkcje w jedną funkcję, sprawdzając typ systemu operacyjnego i zwracając wynik.


7

Działa to również w systemie Linux i Windows ze standardową biblioteką

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]

Wyniki:

['c', 'c', 'c', 'c', 'c', 'c', 'c']

6

Oto rozwiązanie tylko do wyrażenia regularnego, które wydaje się działać z każdą ścieżką systemu operacyjnego w dowolnym systemie operacyjnym.

Nie jest potrzebny żaden inny moduł ani wstępne przetwarzanie:

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

Aktualizacja:

Jeśli chcesz tylko potencjalną nazwę pliku, jeśli jest obecny (czyli /a/b/to dir i tak jest c:\windows\), zmienić regex: r'[^\\/]+(?![\\/])$'. W przypadku „wyrażenia regularnego zakwestionowanego” zmienia to pozytywne spojrzenie w przód dla jakiegoś ukośnika na negatywne spojrzenie w przód, powodując, że ścieżki, które kończą się tym ukośnikiem, nie zwracają niczego zamiast ostatniego podkatalogu w nazwie ścieżki. Oczywiście nie ma gwarancji, że potencjalna nazwa pliku faktycznie odnosi się do pliku i do tego os.path.is_dir()lub os.path.is_file()będzie musiał zostać zastosowany.

Będzie to pasować w następujący sposób:

/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

Wyrażenie regularne można przetestować tutaj .


używasz re, dlaczego nie moduł os?
Saurabh Chandra Patel

@SaurabhChandraPatel minęło sporo czasu. Jeśli dobrze pamiętam, wyrażenie regularne jest w tym przypadku używane jako rozwiązanie wieloplatformowe. Możesz na przykład przetwarzać nazwy plików systemu Windows na serwerze Linux.
Eric Duminil

5

Może po prostu moje wszystko w jednym rozwiązaniu bez ważnych nowych (patrz plik tymczasowy do tworzenia plików tymczasowych: D)

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

Uzyskiwanie wartości abc.namebędzie ciągiem takim: '/tmp/tmpks5oksk7' Mogę zastąpić /spacją, .replace("/", " ")a następnie wywołać split(). To zwróci listę, a ja otrzymam ostatni element listy[-1]

Nie trzeba importować żadnego modułu.


2
Co jeśli nazwa pliku lub katalog zawiera spację?
kriss

1
Co z bezpośrednim podziałem („/”) [- 1]?
Nan

4

Nigdy nie widziałem ścieżek podwójnie odwróconych, czy istnieją? Wbudowana funkcja modułu python oskończy się niepowodzeniem. Wszystkie inne działają, również podane przez ciebie zastrzeżenie dotyczące 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))

To nie są podwójne ukośniki. Są pojedynczymi odwrotnymi ukośnikami i trzeba ich uciec.
Eric Duminil

3

Separator systemu Windows może znajdować się w nazwie pliku Unix lub ścieżce systemu Windows. Separator uniksowy może istnieć tylko na ścieżce uniksowej. Obecność separatora uniksowego wskazuje ścieżkę inną niż Windows.

Poniższe spowoduje usunięcie (odcięcie końcowego separatora) według separatora specyficznego dla systemu operacyjnego, a następnie podzielenie i zwrócenie najwyższej wartości z prawej strony. To brzydkie, ale proste w oparciu o powyższe założenie. Jeśli założenie jest nieprawidłowe, zaktualizuj, a ja zaktualizuję tę odpowiedź, aby pasowała do bardziej dokładnych warunków.

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

przykładowy kod:

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

1
Ponadto zachęcamy do przesłania mi wskazówek dotyczących formatowania w tym miejscu. Pół tuzina próbowało wprowadzić przykładowy kod na miejscu.
dusc2don

1

Dla kompletności, oto pathlibrozwiązanie dla 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']

Działa to zarówno w systemie Windows, jak i Linux.


1

Zarówno w Pythonie 2, jak i 3, używając modułu 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)

Stosowanie:

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

Z twoją walizką testową:

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

Chodzi tutaj o konwersję wszystkich ścieżek w ujednoliconą wewnętrzną reprezentację pathlib2z różnymi dekoderami w zależności od platformy. Na szczęście pathlib2zawiera ogólny nazwany dekoder, PurePathktóry powinien działać na dowolnej ścieżce. Jeśli to nie zadziała, możesz wymusić rozpoznanie ścieżki systemu Windows za pomocą fromwinpath=True. Spowoduje to podzielenie łańcucha wejściowego na części, ostatni to liść, którego szukasz, stąd path2unix(t)[-1].

Jeśli argument nojoin=False, ścieżka zostanie ponownie połączona, tak że wynik będzie po prostu łańcuchem wejściowym przekonwertowanym na format uniksowy, co może być przydatne do porównania podścieżek na różnych platformach.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.