У меня есть путь (включая каталог и имя файла).
Мне нужно проверить, является ли имя файла допустимым, например, если файловая система позволяет мне создать файл с таким именем.
В имени файла есть символы Юникода .
Можно с уверенностью предположить, что сегмент каталога в пути действителен и доступен ( я пытался сделать вопрос более применимым, и, очевидно, я зашел слишком далеко ).
Я очень не хочу убегать от чего-либо, если мне не придется .
Я бы опубликовал некоторые из примеров символов, с которыми я имею дело, но, очевидно, они автоматически удаляются системой обмена стеками. В любом случае, я хочу сохранить стандартные объекты Unicode, такие как ö
, и избегать только тех вещей, которые недопустимы в имени файла.
Вот в чем загвоздка. Возможно (а может и не быть) файл уже находится в целевом значении пути. Мне нужно сохранить этот файл, если он существует, и не создавать файл, если его нет.
В основном я хочу , чтобы проверить , если я мог бы написать на пути , не открывая путь для записи (и автоматическое создание файла / файла затирания , что обычно влечет за собой).
Как таковой:
try:
open(filename, 'w')
except OSError:
# handle error here
Это неприемлемо, потому что он перезапишет существующий файл, к которому я не хочу прикасаться (если он есть), или создаст указанный файл, если это не так.
Я знаю, что могу:
if not os.access(filePath, os.W_OK):
try:
open(filePath, 'w').close()
os.unlink(filePath)
except OSError:
# handle error here
Но это создаст файл в filePath
, который мне тогда придется os.unlink
.
В конце концов, кажется, что он тратит 6 или 7 строк, чтобы сделать что-то столь же простое os.isvalidpath(filePath)
или подобное.
В стороне, мне нужно, чтобы это работало (по крайней мере) в Windows и MacOS, поэтому я бы хотел избежать вещей, зависящих от платформы.
``
источник
Ответы:
tl; dr
Вызовите
is_path_exists_or_creatable()
функцию, определенную ниже.Строго Python 3. Вот так мы и катимся.
Повесть о двух вопросах
Вопрос: «Как мне проверить правильность имени пути и, для действительных имен, наличие или возможность записи этих путей?» это явно два отдельных вопроса. Оба они интересны, и ни один из них не получил действительно удовлетворительного ответа здесь ... или, ну, в любом месте , где я мог бы grep.
Викки «s ответ , вероятно , рубит ближе, но имеет замечательные недостатки:
Мы все это исправим.
Вопрос № 0: что еще раз озвучивает действительность имени пути?
Прежде чем швырять наши хрупкие мясные скафандры в кишащие питонами мошпиты боли, нам, вероятно, следует определить, что мы подразумеваем под «достоверностью имени пути». Что конкретно определяет действительность?
Под «достоверностью имени пути» мы подразумеваем синтаксическую правильность имени пути по отношению к корневой файловой системе текущей системы - независимо от того, существует ли физически этот путь или его родительские каталоги. Имя пути является синтаксически правильным в соответствии с этим определением, если оно соответствует всем синтаксическим требованиям корневой файловой системы.
Под "корневой файловой системой" мы подразумеваем:
/
).%HOMEDRIVE%
букве диска с суффиксом двоеточия, содержащей текущую установку Windows (обычно, но не обязательноC:
).Значение «синтаксической правильности», в свою очередь, зависит от типа корневой файловой системы. Для
ext4
(и для большинства, но не для всех POSIX-совместимых) файловых систем имя пути является синтаксически правильным тогда и только тогда, когда это имя пути:\x00
в Python). Это жесткое требование для всех файловых систем, совместимых с POSIX.'a'*256
в Python). Компонент пути является самым длинной подстрокой имени пути , не содержащим/
символ (например,bergtatt
,ind
,i
, иfjeldkamrene
в путевом имени/bergtatt/ind/i/fjeldkamrene
).Синтаксическая корректность. Корневая файловая система. Вот и все.
Вопрос №1: как теперь обеспечить валидность имени пути?
Проверка путей в Python на удивление не интуитивно понятна. Я полностью согласен с Fake Name здесь: официальный
os.path
пакет должен предоставлять готовое решение для этого. По неизвестным (и, вероятно, неопровержимым) причинам это не так. К счастью, разворачивая собственное одноранговой решение не что выворачивающий ...Хорошо, это действительно так. Волосатый; это мерзко; он, вероятно, хихикает, бормоча, и хихикает, когда светится. Но что ты собираешься делать? Nuthin '.
Скоро мы спустимся в радиоактивную бездну низкоуровневого кода. Но сначала поговорим о магазине высокого уровня. Стандарт
os.stat()
иos.lstat()
функции вызывают следующие исключения при передаче недопустимых путей:FileNotFoundError
.WindowsError
,winerror
атрибут которых равен123
(т. Е.ERROR_INVALID_NAME
).'\x00'
), ЭкземплярыTypeError
.OSError
которыхerrcode
атрибут является:errno.ERANGE
. (Похоже, это ошибка уровня ОС, иначе называемая «выборочной интерпретацией» стандарта POSIX.)errno.ENAMETOOLONG
.Что особенно важно, это означает, что проверяемы только пути, находящиеся в существующих каталогах. Функции
os.stat()
иos.lstat()
вызывают общиеFileNotFoundError
исключения, когда передаются пути, находящиеся в несуществующих каталогах, независимо от того, являются ли эти пути недопустимыми или нет. Существование каталога имеет приоритет над недействительностью имени пути.Означает ли это, что пути, находящиеся в несуществующих каталогах, нельзя проверить? Да - если мы не изменим эти пути для размещения в существующих каталогах. Однако возможно ли это даже безопасно? Разве изменение имени пути не должно помешать нам проверить исходное имя пути?
Чтобы ответить на этот вопрос, вспомните выше, что синтаксически правильные имена путей в
ext4
файловой системе не содержат компонентов пути (A), содержащих нулевые байты, или (B) длиной более 255 байтов. Следовательно, имяext4
пути допустимо тогда и только тогда, когда все компоненты пути в этом имени пути допустимы. Это верно для большинства представляющих интерес реальных файловых систем.Действительно ли нам помогает эта педантичная проницательность? Да. Это сокращает большую проблему проверки полного имени пути одним махом до меньшей проблемы проверки только всех компонентов пути в этом имени пути. Любое произвольное имя пути может быть проверено (независимо от того, находится ли этот путь в существующем каталоге или нет) кроссплатформенным способом, следуя следующему алгоритму:
/troldskog/faren/vild
в списке['', 'troldskog', 'faren', 'vild']
)./troldskog
).os.stat()
илиos.lstat()
. Если этот путь и, следовательно, этот компонент недопустим, этот вызов гарантированно вызовет исключение, раскрывающее тип недействительности, а не общееFileNotFoundError
исключение. Зачем? Поскольку этот путь находится в существующем каталоге. (Круговая логика круговая.)Гарантированно ли существует каталог? Да, но обычно только один: самый верхний каталог корневой файловой системы (как определено выше).
Передача имен путей, находящихся в любом другом каталоге (и, следовательно, не гарантируется их существование),
os.stat()
илиos.lstat()
вызывает условия гонки, даже если этот каталог ранее был протестирован на существование. Зачем? Поскольку нельзя предотвратить одновременное удаление этого каталога внешними процессами после того, как этот тест был выполнен, но до того, как этот путь будет передан вos.stat()
илиos.lstat()
. Развяжите псов головокружительного безумия!У вышеупомянутого подхода также есть существенное побочное преимущество: безопасность. (Не что приятно?) В частности:
Вышеупомянутый подход устраняет это, только проверяя компоненты пути в имени пути относительно корневого каталога корневой файловой системы. (Если даже это устаревшее, медленное или недоступное, у вас есть более серьезные проблемы, чем проверка имени пути.)
Потерянный? Отлично. Давайте начнем. (Предполагается Python 3. См. «Что такое хрупкая надежда для 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?
Выполнено. Не прищуривайся на этот код. ( Кусает. )
Вопрос № 2: Возможно, неверное существование или возможность существования пути, а?
Проверка существования или возможности создания возможно недопустимых имен путей, учитывая вышеупомянутое решение, в основном тривиально. Маленький ключ здесь - вызвать ранее определенную функцию перед проверкой пройденного пути:
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
Готово и сделано. Только не совсем.
Вопрос № 3: Возможно, неверное существование пути или возможность записи в Windows
Есть нюанс. Конечно, есть.
Как гласит официальная
os.access()
документация :Неудивительно, что обычным подозреваемым здесь является Windows. Благодаря широкому использованию списков контроля доступа (ACL) в файловых системах NTFS упрощенная модель битов разрешений POSIX плохо соответствует реальности Windows. Хотя это (возможно) не вина Python, тем не менее это может вызывать беспокойство для Windows-совместимых приложений.
Если это вы, нужна более надежная альтернатива. Если переданный путь не существует, мы вместо этого пытаемся создать временный файл, который гарантированно будет немедленно удален в родительском каталоге этого пути - более переносимый (если дорогой) тест на возможность создания:
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
Учтите, однако, что даже этого может быть недостаточно.
Благодаря контролю доступа пользователей (UAC) неподражаемая Windows Vista и все ее последующие версии откровенно лгут о разрешениях, относящихся к системным каталогам. Когда пользователи не администраторы пытаются создать файлы в любом каноническом
C:\Windows
илиC:\Windows\system32
каталогах, UAC внешне позволяет пользователю сделать это в то время как на самом деле изолировать все созданные файлы в «виртуальный магазин» в профиле этого пользователя. (Кто бы мог подумать, что обман пользователей приведет к пагубным долгосрочным последствиям?)Это безумие. Это винда.
Докажите это
Смеем ли мы? Пришло время протестировать вышеперечисленные тесты.
Поскольку NULL - единственный символ, запрещенный в именах путей в файловых системах, ориентированных на UNIX, давайте воспользуемся этим, чтобы продемонстрировать холодную, суровую истину - игнорировать неотвратимые махинации Windows, которые, откровенно говоря, в равной степени раздражают и злят меня:
>>> 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
За гранью здравомыслия. Помимо боли. Вы найдете проблемы переносимости Python.
источник
is_
. Это недостаток моего характера. Тем не менее, как следует отметить: нельзя всем угодить, а иногда никому не угодишь. ;)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
Обратите внимание, что
path.exists
можетthe file is not there
произойти сбой по большему количеству причин, чем просто потому, что вам, возможно, придется выполнить более тонкие тесты, такие как проверка, существует ли содержащий каталог и так далее.После моего обсуждения с OP выяснилось, что основная проблема, похоже, заключается в том, что имя файла может содержать символы, которые не разрешены файловой системой. Конечно, их нужно удалить, но OP хочет поддерживать столько читабельности, сколько позволяет файловая система.
К сожалению, я не знаю хорошего решения для этого. Однако в ответе Сесила Карри более подробно рассматривается обнаружение проблемы.
источник
or can be created
ну, я не прочитал это из вашего вопроса. Чтение разрешений будет в некоторой степени зависеть от платформы.os.path.exists(filePath)
технически вызывает исключения для недопустимых имен путей, эти исключения необходимо явно перехватывать и отличать от других несвязанных исключений. Более того, тот же вызов возвращаетсяFalse
на существующие пути, для которых текущий пользователь не имеет прав на чтение. Короче, беда.Что насчет Python 3:
try: with open(filename, 'x') as tempfile: # OSError if file exists or is invalid pass except OSError: # handle error here
С опцией «x» нам также не нужно беспокоиться об условиях гонки. Смотрите документацию здесь .
Теперь это БУДЕТ создавать временный файл с очень коротким сроком жизни, если он еще не существует - если только имя не является недопустимым. Если вы можете смириться с этим, это значительно упростит ситуацию.
источник
open(filename,'r') #2nd argument is r and not w
откроет файл или выдаст ошибку, если он не существует. Если есть ошибка, вы можете попробовать записать в путь, если не можете, вы получите вторую ошибку
try: open(filename,'r') return True except IOError: try: open(filename, 'w') return True except IOError: return False
Также посмотрите здесь о разрешениях на окнах
источник
tempfile.TemporaryFile()
который автоматически уничтожит временный файл, когда он выйдет за пределы области видимости.os.path.join
, поэтому у меня нет проблем с побегом. Кроме того, у меня действительно нет проблем с правами доступа к каталогам . У меня проблемы с именем каталога (и имени файла) .filename
содержит недопустимые символы. Я отредактировал ответпопробуйте,
os.path.exists
это проверит путь и вернется,True
если он существует, аFalse
если нет.источник