Mam ścieżkę (w tym katalog i nazwę pliku).
Muszę sprawdzić, czy nazwa-pliku jest prawidłowa, np. Czy system plików pozwoli mi stworzyć plik o takiej nazwie.
Nazwa pliku zawiera kilka znaków Unicode .
Można bezpiecznie założyć, że segment katalogu ścieżki jest prawidłowy i dostępny ( starałem się, aby pytanie było bardziej ogólne i najwyraźniej dotarłem za daleko ).
Bardzo nie chcę uciekać przed czymkolwiek, chyba że muszę .
Zamieszczałbym kilka przykładowych postaci, z którymi mam do czynienia, ale najwyraźniej są one automatycznie usuwane przez system wymiany stosów. W każdym razie chcę zachować standardowe jednostki Unicode, takie jak ö
i tylko uciec przed rzeczami, które są nieprawidłowe w nazwie pliku.
Oto haczyk. Plik docelowy ścieżki może (ale nie musi) już istnieć. Muszę zachować ten plik, jeśli istnieje, i nie tworzyć go, jeśli nie istnieje.
Zasadniczo chcę, aby sprawdzić, czy ja mógł pisać do ścieżki bez faktycznie otwierając drogę do pisania (i automatyczne tworzenie pliku / file przebijania, które zwykle pociąga za sobą).
Takie jak:
try:
open(filename, 'w')
except OSError:
# handle error here
Jest nie do przyjęcia, ponieważ nadpisze istniejący plik, którego nie chcę dotykać (jeśli tam jest), lub utworzy wspomniany plik, jeśli go nie ma.
Wiem, że potrafię:
if not os.access(filePath, os.W_OK):
try:
open(filePath, 'w').close()
os.unlink(filePath)
except OSError:
# handle error here
Ale to utworzy plik w katalogu filePath
, który musiałbym wtedy zrobić os.unlink
.
W końcu wygląda na to, że spędza 6 lub 7 linii, aby zrobić coś, co powinno być tak proste os.isvalidpath(filePath)
lub podobne.
Na marginesie, potrzebuję tego do działania (przynajmniej) na Windows i MacOS, więc chciałbym uniknąć rzeczy specyficznych dla platformy.
``
źródło
os.path.isabs(PATH)
, ale to nie obejmuje ścieżki względnej :-(.Odpowiedzi:
tl; dr
Zadzwoń do
is_path_exists_or_creatable()
funkcję zdefiniowaną poniżej.Ściśle Python 3. Tak właśnie działamy.
Opowieść o dwóch pytaniach
Pytanie „Jak sprawdzić poprawność nazw ścieżek i, w przypadku prawidłowych ścieżek, istnienie lub możliwość zapisu tych ścieżek?” to wyraźnie dwa oddzielne pytania. Obie są interesujące i żadna z nich nie otrzymała tutaj naprawdę satysfakcjonującej odpowiedzi ... lub, cóż, gdziekolwiek , gdzie mogłem grep.
Vikki jest odpowiedź prawdopodobnie hews najbliżej, ale ma niezwykłe wady:
Naprawimy to wszystko.
Pytanie # 0: Jaka jest ponownie ważność ścieżki?
Zanim wrzucimy nasze kruche kombinezony mięsne w podziurawione pytonami moshpity bólu, powinniśmy prawdopodobnie zdefiniować, co rozumiemy przez „ważność ścieżki”. Co dokładnie definiuje ważność?
Przez „ważność ścieżki” rozumiemy poprawność składniową ścieżki w odniesieniu do głównego systemu plików bieżącego systemu - niezależnie od tego, czy ta ścieżka lub jej katalogi nadrzędne istnieją fizycznie. W ramach tej definicji nazwa ścieżki jest poprawna składniowo, jeśli spełnia wszystkie wymagania składniowe głównego systemu plików.
Przez „główny system plików” rozumiemy:
/
).%HOMEDRIVE%
, litera dysku okrężnicy sufiksem zawierającym bieżącej instalacji systemu Windows (zazwyczaj, ale nie koniecznieC:
).Z kolei znaczenie „poprawności składniowej” zależy od typu głównego systemu plików. Dla
ext4
(i większości, ale nie wszystkich zgodnych z POSIX) systemów plików, nazwa ścieżki jest poprawna składniowo wtedy i tylko wtedy, gdy ta nazwa ścieżki:\x00
W Pythonie). Jest to trudne wymaganie dla wszystkich systemów plików zgodnych z POSIX.'a'*256
W Pythonie). Składnik ścieżka jest najdłuższy podciąg z ścieżki nie zawierającej/
znak (na przykładbergtatt
,ind
,i
orazfjeldkamrene
w ścieżkę/bergtatt/ind/i/fjeldkamrene
).Poprawność składniowa. Główny system plików. Otóż to.
Pytanie 1: Jak teraz powinniśmy sprawdzić ważność nazwy ścieżki?
Weryfikacja ścieżek w Pythonie jest zaskakująco nieintuicyjna. W tym przypadku zgadzam się z Fake Name : oficjalny
os.path
pakiet powinien zapewniać gotowe rozwiązanie tego problemu. Z nieznanych (i prawdopodobnie niekwestionowanych) powodów tak nie jest. Na szczęście rozwijanie własnego rozwiązania ad hoc nie jest aż tak bolesne ...OK, faktycznie jest. Jest włochaty; to jest paskudne; prawdopodobnie chichocze, burczy i chichocze, gdy się świeci. Ale co zrobisz? Nic.
Wkrótce zejdziemy w radioaktywną otchłań niskopoziomowego kodu. Ale najpierw porozmawiajmy o sklepie na wysokim poziomie. Standard
os.stat()
ios.lstat()
funkcje zgłaszają następujące wyjątki po przekazaniu nieprawidłowych nazw ścieżek:FileNotFoundError
.WindowsError
którychwinerror
atrybut to123
(tjERROR_INVALID_NAME
.).'\x00'
), WystąpieniaTypeError
.OSError
którycherrcode
atrybutu to:errno.ERANGE
. (Wydaje się, że jest to błąd na poziomie systemu operacyjnego, inaczej określany jako „selektywna interpretacja” standardu POSIX).errno.ENAMETOOLONG
.Co najważniejsze, oznacza to, że tylko ścieżki znajdujące się w istniejących katalogach są weryfikowalne. Funkcje
os.stat()
ios.lstat()
zgłaszają ogólneFileNotFoundError
wyjątki, gdy przekazywane są nazwy ścieżek rezydujące w nieistniejących katalogach, niezależnie od tego, czy te nazwy ścieżek są nieprawidłowe, czy nie. Istnienie katalogu ma pierwszeństwo przed nieważnością nazwy ścieżki.Czy to oznacza, że nazwy ścieżek znajdujące się w nieistniejących katalogach nie podlegają walidacji? Tak - chyba że zmodyfikujemy te ścieżki, aby znajdowały się w istniejących katalogach. Czy jest to jednak w ogóle możliwe do wykonania? Czy modyfikacja nazwy ścieżki nie powinna uniemożliwić nam sprawdzenia oryginalnej ścieżki?
Aby odpowiedzieć na to pytanie, przypomnij sobie z góry, że poprawne składniowo nazwy ścieżek w
ext4
systemie plików nie zawierają składników ścieżki (A) zawierających bajty zerowe lub (B) o długości przekraczającej 255 bajtów. Dlategoext4
nazwa ścieżki jest poprawna wtedy i tylko wtedy, gdy wszystkie składniki ścieżki w tej nazwie ścieżki są prawidłowe. Dotyczy to większości interesujących nas systemów plików w świecie rzeczywistym .Czy ten pedantyczny wgląd rzeczywiście nam pomaga? Tak. Zmniejsza to większy problem związany z walidacją pełnej nazwy ścieżki za jednym zamachem do mniejszego problemu sprawdzania poprawności tylko wszystkich składników ścieżki w tej nazwie ścieżki. Dowolną dowolną nazwę ścieżki można zweryfikować (niezależnie od tego, czy ta ścieżka znajduje się w istniejącym katalogu, czy nie) w sposób wieloplatformowy, stosując następujący algorytm:
/troldskog/faren/vild
do listy['', 'troldskog', 'faren', 'vild']
)./troldskog
.).os.stat()
lubos.lstat()
. Jeśli ta nazwa ścieżki, a tym samym ten składnik, jest nieprawidłowa, to wywołanie gwarantuje zgłoszenie wyjątku ujawniającego typ nieważności, a nieFileNotFoundError
wyjątek ogólny . Czemu? Ponieważ ta nazwa ścieżki znajduje się w istniejącym katalogu. (Logika kołowa jest kołowa.)Czy istnieje katalog na pewno istnieje? Tak, ale zazwyczaj tylko jeden: najwyższy katalog głównego systemu plików (zgodnie z powyższą definicją).
Przekazywanie nazw ścieżek znajdujących się w jakimkolwiek innym katalogu (a zatem nie gwarantuje się, że istnieją) do warunków wyścigu
os.stat()
lub powodujeos.lstat()
zaproszenie do warunków wyścigu, nawet jeśli wcześniej sprawdzono, czy katalog ten istnieje. Czemu? Ponieważ nie można zapobiec równoczesnemu usuwaniu tego katalogu przez procesy zewnętrzne po wykonaniu tego testu, ale przed przekazaniem tejos.stat()
nazwy ścieżki do lubos.lstat()
. Uwolnij psy oszałamiającego szaleństwa!Powyższe podejście ma również istotną zaletę: bezpieczeństwo. (Nie jest to miłe?) W szczególności:
Powyższe podejście zapobiega temu, sprawdzając tylko składniki ścieżki ścieżki względem katalogu głównego głównego systemu plików. (Jeśli nawet to jest nieaktualne, powolne lub niedostępne, masz większe problemy niż weryfikacja ścieżki).
Stracony? Świetny. Zaczynajmy. (Założono Python 3. Zobacz „What Is Fragile Hope for 300, leycec ?”)
import errno, os # Sadly, Python fails to provide the following magic number for us. ERROR_INVALID_NAME = 123 ''' Windows-specific error code indicating an invalid pathname. See Also ---------- https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes--0-499- Official listing of all such codes. ''' def is_pathname_valid(pathname: str) -> bool: ''' `True` if the passed pathname is a valid pathname for the current OS; `False` otherwise. ''' # If this pathname is either not a string or is but is empty, this pathname # is invalid. try: if not isinstance(pathname, str) or not pathname: return False # Strip this pathname's Windows-specific drive specifier (e.g., `C:\`) # if any. Since Windows prohibits path components from containing `:` # characters, failing to strip this `:`-suffixed prefix would # erroneously invalidate all valid absolute Windows pathnames. _, pathname = os.path.splitdrive(pathname) # Directory guaranteed to exist. If the current OS is Windows, this is # the drive to which Windows was installed (e.g., the "%HOMEDRIVE%" # environment variable); else, the typical root directory. root_dirname = os.environ.get('HOMEDRIVE', 'C:') \ if sys.platform == 'win32' else os.path.sep assert os.path.isdir(root_dirname) # ...Murphy and her ironclad Law # Append a path separator to this directory if needed. root_dirname = root_dirname.rstrip(os.path.sep) + os.path.sep # Test whether each path component split from this pathname is valid or # not, ignoring non-existent and non-readable path components. for pathname_part in pathname.split(os.path.sep): try: os.lstat(root_dirname + pathname_part) # If an OS-specific exception is raised, its error code # indicates whether this pathname is valid or not. Unless this # is the case, this exception implies an ignorable kernel or # filesystem complaint (e.g., path not found or inaccessible). # # Only the following exceptions indicate invalid pathnames: # # * Instances of the Windows-specific "WindowsError" class # defining the "winerror" attribute whose value is # "ERROR_INVALID_NAME". Under Windows, "winerror" is more # fine-grained and hence useful than the generic "errno" # attribute. When a too-long pathname is passed, for example, # "errno" is "ENOENT" (i.e., no such file or directory) rather # than "ENAMETOOLONG" (i.e., file name too long). # * Instances of the cross-platform "OSError" class defining the # generic "errno" attribute whose value is either: # * Under most POSIX-compatible OSes, "ENAMETOOLONG". # * Under some edge-case OSes (e.g., SunOS, *BSD), "ERANGE". except OSError as exc: if hasattr(exc, 'winerror'): if exc.winerror == ERROR_INVALID_NAME: return False elif exc.errno in {errno.ENAMETOOLONG, errno.ERANGE}: return False # If a "TypeError" exception was raised, it almost certainly has the # error message "embedded NUL character" indicating an invalid pathname. except TypeError as exc: return False # If no exception was raised, all path components and hence this # pathname itself are valid. (Praise be to the curmudgeonly python.) else: return True # If any other exception was raised, this is an unrelated fatal issue # (e.g., a bug). Permit this exception to unwind the call stack. # # Did we mention this should be shipped with Python already?
Gotowe. Nie mruż oczy na ten kod. ( Gryzie. )
Pytanie 2: Możliwe, że istnieje nieprawidłowa nazwa ścieżki lub możliwość jej wykonania, co?
Testowanie istnienia lub możliwości tworzenia potencjalnie nieprawidłowych nazw ścieżek jest, biorąc pod uwagę powyższe rozwiązanie, w większości trywialne. Mały klucz polega na wywołaniu wcześniej zdefiniowanej funkcji przed przetestowaniem przekazanej ścieżki:
def is_path_creatable(pathname: str) -> bool: ''' `True` if the current user has sufficient permissions to create the passed pathname; `False` otherwise. ''' # Parent directory of the passed path. If empty, we substitute the current # working directory (CWD) instead. dirname = os.path.dirname(pathname) or os.getcwd() return os.access(dirname, os.W_OK) def is_path_exists_or_creatable(pathname: str) -> bool: ''' `True` if the passed pathname is a valid pathname for the current OS _and_ either currently exists or is hypothetically creatable; `False` otherwise. This function is guaranteed to _never_ raise exceptions. ''' try: # To prevent "os" module calls from raising undesirable exceptions on # invalid pathnames, is_pathname_valid() is explicitly called first. return is_pathname_valid(pathname) and ( os.path.exists(pathname) or is_path_creatable(pathname)) # Report failure on non-fatal filesystem complaints (e.g., connection # timeouts, permissions issues) implying this path to be inaccessible. All # other exceptions are unrelated fatal issues and should not be caught here. except OSError: return False
Gotowe i gotowe. Z wyjątkiem niezupełnie.
Pytanie # 3: Prawdopodobnie nieprawidłowe istnienie ścieżki lub możliwość zapisu w systemie Windows
Istnieje zastrzeżenie. Oczywiście, że tak.
Jak przyznaje oficjalna
os.access()
dokumentacja :Nic dziwnego, że podejrzanym jest tutaj zwykle Windows. Dzięki szerokiemu wykorzystaniu list kontroli dostępu (ACL) w systemach plików NTFS, uproszczony model bitowy uprawnień POSIX słabo odwzorowuje rzeczywistość systemu Windows. Chociaż nie jest to (prawdopodobnie) wina Pythona, może to jednak dotyczyć aplikacji zgodnych z systemem Windows.
Jeśli to ty, potrzebna jest solidniejsza alternatywa. Jeśli przekazana ścieżka nie istnieje, zamiast tego spróbujemy utworzyć plik tymczasowy z gwarancją natychmiastowego usunięcia w katalogu nadrzędnym tej ścieżki - bardziej przenośny (jeśli kosztowny) test kreatywności:
import os, tempfile def is_path_sibling_creatable(pathname: str) -> bool: ''' `True` if the current user has sufficient permissions to create **siblings** (i.e., arbitrary files in the parent directory) of the passed pathname; `False` otherwise. ''' # Parent directory of the passed path. If empty, we substitute the current # working directory (CWD) instead. dirname = os.path.dirname(pathname) or os.getcwd() try: # For safety, explicitly close and hence delete this temporary file # immediately after creating it in the passed path's parent directory. with tempfile.TemporaryFile(dir=dirname): pass return True # While the exact type of exception raised by the above function depends on # the current version of the Python interpreter, all such types subclass the # following exception superclass. except EnvironmentError: return False def is_path_exists_or_creatable_portable(pathname: str) -> bool: ''' `True` if the passed pathname is a valid pathname on the current OS _and_ either currently exists or is hypothetically creatable in a cross-platform manner optimized for POSIX-unfriendly filesystems; `False` otherwise. This function is guaranteed to _never_ raise exceptions. ''' try: # To prevent "os" module calls from raising undesirable exceptions on # invalid pathnames, is_pathname_valid() is explicitly called first. return is_pathname_valid(pathname) and ( os.path.exists(pathname) or is_path_sibling_creatable(pathname)) # Report failure on non-fatal filesystem complaints (e.g., connection # timeouts, permissions issues) implying this path to be inaccessible. All # other exceptions are unrelated fatal issues and should not be caught here. except OSError: return False
Należy jednak pamiętać, że nawet to może nie wystarczyć.
Dzięki kontroli dostępu użytkownika (UAC), zawsze niemożliwy do wyobrażenia system Windows Vista i wszystkie jego kolejne iteracje rażąco kłamie na temat uprawnień odnoszących się do katalogów systemowych. Kiedy użytkownicy niebędący administratorami próbują tworzyć pliki w katalogu kanonicznym
C:\Windows
lubC:\Windows\system32
katalogach, UAC powierzchownie zezwala użytkownikowi na to, podczas gdy w rzeczywistości izoluje wszystkie utworzone pliki do „magazynu wirtualnego” w profilu tego użytkownika. (Kto mógł sobie wyobrazić, że oszukiwanie użytkowników będzie miało szkodliwe długoterminowe konsekwencje?)To jest szalone. To jest Windows.
Udowodnij to
Czy mamy odwagę? Czas przetestować powyższe testy.
Ponieważ NULL jest jedyną postacią zabronioną w nazwach ścieżek w systemach plików zorientowanych na UNIX, wykorzystajmy to, aby zademonstrować zimną, twardą prawdę - ignorując nie dające się zignorować shenanigans Windows, które szczerze mnie nudzą i złości w równym stopniu:
>>> print('"foo.bar" valid? ' + str(is_pathname_valid('foo.bar'))) "foo.bar" valid? True >>> print('Null byte valid? ' + str(is_pathname_valid('\x00'))) Null byte valid? False >>> print('Long path valid? ' + str(is_pathname_valid('a' * 256))) Long path valid? False >>> print('"/dev" exists or creatable? ' + str(is_path_exists_or_creatable('/dev'))) "/dev" exists or creatable? True >>> print('"/dev/foo.bar" exists or creatable? ' + str(is_path_exists_or_creatable('/dev/foo.bar'))) "/dev/foo.bar" exists or creatable? False >>> print('Null byte exists or creatable? ' + str(is_path_exists_or_creatable('\x00'))) Null byte exists or creatable? False
Poza zdrowiem psychicznym. Poza bólem. Znajdziesz obawy dotyczące przenośności języka Python.
źródło
is_
. To jest wada mojego charakteru. Niemniej jednak, należycie zauważono: nie możesz zadowolić wszystkich, a czasami nie możesz nikogo zadowolić. ;)if os.path.exists(filePath): #the file is there elif os.access(os.path.dirname(filePath), os.W_OK): #the file does not exists but write privileges are given else: #can not write there
Zauważ, że
path.exists
może się to nie powieść z wielu powodów, niż tylkothe file is not there
dlatego, że być może będziesz musiał wykonać dokładniejsze testy, takie jak testowanie, jeśli katalog zawierający taki istnieje i tak dalej.Po mojej dyskusji z OP okazało się, że głównym problemem wydaje się być to, że nazwa pliku może zawierać znaki niedozwolone przez system plików. Oczywiście należy je usunąć, ale OP chce zachować tyle czytelności dla ludzi, na ile pozwala system plików.
Niestety nie znam dobrego rozwiązania tego problemu. Jednak odpowiedź Cecila Curry'ego przygląda się bliżej wykryciu problemu.
źródło
or can be created
cóż, nie przeczytałem tego z twojego pytania. Odczytywanie uprawnień będzie do pewnego stopnia zależne od platformy.os.path.exists(filePath)
technicznie generuje wyjątki dla nieprawidłowych nazw ścieżek, te wyjątki musiałyby zostać jawnie przechwycone i odróżnione od innych niepowiązanych wyjątków. Ponadto to samo wywołanie powracaFalse
na istniejących ścieżkach, do których bieżący użytkownik nie ma uprawnień do odczytu. Krótko mówiąc, zło.W przypadku Pythona 3, co powiesz na:
try: with open(filename, 'x') as tempfile: # OSError if file exists or is invalid pass except OSError: # handle error here
Dzięki opcji „x” nie musimy też martwić się o warunki wyścigu. Zobacz dokumentację tutaj .
Teraz spowoduje to utworzenie bardzo krótkotrwałego pliku tymczasowego, jeśli jeszcze nie istnieje - chyba że nazwa jest nieprawidłowa. Jeśli możesz z tym żyć, to bardzo upraszcza sprawę.
źródło
open(filename,'r') #2nd argument is r and not w
otworzy plik lub wyświetli błąd, jeśli nie istnieje. Jeśli wystąpi błąd, możesz spróbować napisać do ścieżki, jeśli nie możesz, pojawi się drugi błąd
try: open(filename,'r') return True except IOError: try: open(filename, 'w') return True except IOError: return False
Zajrzyj tutaj również na temat uprawnień w systemie Windows
źródło
tempfile.TemporaryFile()
która automatycznie zniszczy plik tymczasowy, gdy wyjdzie poza zakres.os.path.join
, więc nie mam problemów z ucieczką. Co więcej, tak naprawdę nie mam problemów z uprawnieniami do katalogu . Mam problemy z nazwą katalogu (i nazwy pliku) .filename
zawiera nieprawidłowe znaki.spróbuj
os.path.exists
to sprawdzi ścieżkę i zwróci,True
jeśli istnieje, aFalse
jeśli nie.źródło