файлу Проверьте, действителен ли путь на Python, не создавая файл в целевом пути




python указать путь к модулю (4)

ТЛ; др

Вызовите is_path_exists_or_creatable() определенную ниже.

Строго Python 3. Вот как мы катимся.

Сказка о двух вопросах

Вопрос «Как проверить правильность имени пути и, допустимые пути, существование или возможность записи этих путей?» очевидно, два отдельных вопроса. Оба интересны, и ни один из них не получил действительно удовлетворительного ответа здесь ... или, ну, где угодно, что я мог бы grep.

vikki , вероятно, самый близкий, но имеет замечательные недостатки:

  • Неправильно открывается ( ... и затем не удается достоверно закрыть ) файлы.
  • Излишне писать ( ... и затем не удалять надежные файлы с закрытием или удалением ) 0-байтов.
  • Игнорирование ошибок, специфичных для ОС, для дифференциации недопустимых недопустимых путей и проблем с файлами неизвестных. Неудивительно, что это критически важно для Windows. ( См. Ниже ).
  • Игнорирование условий гонки, возникающих в результате внешних процессов, одновременно (пере) перемещение родительских каталогов проверяемого пути. ( См. Ниже ).
  • Игнорирование тайм-аутов соединений, возникающих в результате этого пути, находящихся на устаревших, медленных или временно временно недоступных файловых системах. Это может предоставить публичные услуги для потенциальных DoS приёмов. ( См. Ниже ).

Мы все исправим.

Вопрос № 0: Снова ли снова указана причина?

Перед тем, как бросить наши хрупкие мясные костюмы в пронизанные питоном мошпиты боли, мы должны, вероятно, определить, что мы подразумеваем под «правдой пути». Что конкретно определяет правильность?

Под «допустимостью имени пути» понимается синтаксическая правильность пути по отношению к корневой файловой системе текущей системы - независимо от того, физически ли существует этот путь или родительские каталоги. Имя пути синтаксически корректно под этим определением, если оно соответствует всем синтаксическим требованиям корневой файловой системы.

Под «корневой файловой системой» мы понимаем:

  • В POSIX-совместимых системах файловая система монтируется в корневой каталог ( / ).
  • В Windows файловая система установлена ​​на %HOMEDRIVE% , букву диска с двоеточием, содержащую текущую установку Windows (обычно, но не обязательно C: .

Значение «синтаксической корректности», в свою очередь, зависит от типа корневой файловой системы. Для файловых систем ext4 (и большинства, но не всех POSIX-совместимых) имя пути синтаксически корректно тогда и только тогда, когда этот путь:

  • Не содержит нулевых байтов (т. \x00 в Python). Это сложное требование для всех POSIX-совместимых файловых систем.
  • Не содержит компонентов пути длиной более 255 байт (например, 'a'*256 в Python). Компонент пути - это самая длинная подстрока пути, не содержащая символа no / character (например, bergtatt , ind , i и fjeldkamrene в пути /bergtatt/ind/i/fjeldkamrene ).

Синтаксическая корректность. Корневая файловая система. Вот и все.

Вопрос №1: Как теперь мы будем иметь правильность пути?

Проверка путей в Python на удивление не интуитивно понятна. Я твердо согласен с здесь: официальный пакет os.path должен предоставить готовое решение для этого. Для неизвестных (и, вероятно, несовпадающих) причин это не так. К счастью, развернуть свое собственное ad-hoc решение - это не то, что кишит ...

Хорошо, на самом деле. Это волосатый; это противно; он, вероятно, шутит, когда он бурлит и хихикает, когда он светится. Но что ты собираешься делать? Nuthin.

Мы скоро спустимся в радиоактивную пропасть низкоуровневого кода. Но сначала поговорим о магазине высокого уровня. Стандартные функции os.stat() и os.lstat() вызывают следующие исключения при передаче недопустимых путей:

  • Для путей, которые находятся в несуществующих каталогах, экземпляры FileNotFoundError .
  • Для путей, которые находятся в существующих каталогах:
    • В Windows экземпляры WindowsError чей атрибут winerror равен 123 (т. ERROR_INVALID_NAME ).
    • Под всеми другими операционными системами:
    • Для путей, содержащих нулевые байты (т. '\x00' ), экземпляры TypeError .
    • Для путей, содержащих компоненты пути длиной более 255 байт, экземпляры OSError чей атрибут errcode :
      • В SunOS и в семействе ОС BSD, errno.ERANGE . (Это, по-видимому, ошибка на уровне ОС, иначе называемая «выборочная интерпретация» стандарта POSIX.)
      • Во всех других операционных системах errno.ENAMETOOLONG .

Реально, это означает, что только пути, которые находятся в существующих каталогах, являются допустимыми. Функции os.stat() и os.lstat() поднимают общие исключения FileNotFoundError при передаче путей, находящихся в несуществующих каталогах, независимо от того, являются ли эти пути недопустимыми или нет. Существование каталога имеет приоритет над недействительностью пути.

Означает ли это, что пути, находящиеся в несуществующих каталогах, не поддаются проверке? Да - если мы не изменим эти пути, чтобы они находились в существующих каталогах. Однако это даже безопасно? Не следует ли изменять имя пути, чтобы мы не проверяли исходный путь?

Чтобы ответить на этот вопрос, напомните выше, что синтаксически правильные пути в файловой системе ext4 не содержат компонентов пути (A), содержащих нулевые байты или (B) длиной более 255 байт. Следовательно, путь ext4 действителен тогда и только тогда, когда все компоненты пути в этом пути действительны. Это справедливо для большинства реальных файловых систем реального мира .

Значит ли это педантичное понимание помогает нам? Да. Это уменьшает большую проблему проверки полного имени пути одним махом на меньшую проблему только проверки всех компонентов пути в этом пути. Любое произвольное имя пути является допустимым (независимо от того, находится ли этот путь в существующем каталоге или нет) кросс-платформенным способом, следуя следующему алгоритму:

  1. Разделите это имя пути на компоненты пути (например, путь /troldskog/faren/vild в список ['', 'troldskog', 'faren', 'vild'] ).
  2. Для каждого такого компонента:
    1. Присоедините путь к каталогу, который должен существовать с этим компонентом, в новое временное имя пути (например, /troldskog ).
    2. Передайте этот путь к os.stat() или os.lstat() . Если это имя пути и, следовательно, этот компонент недействительны, этот вызов гарантированно создает исключение, отображающее тип недействительности, а не общее исключение FileNotFoundError . Зачем? Поскольку этот путь находится в существующем каталоге. (Круговая логика является круговой.)

Существует ли каталог, который может существовать? Да, но обычно только один: самый верхний каталог корневой файловой системы (как определено выше).

Передача путей, находящихся в любом другом каталоге (и, следовательно, не гарантированного существования) в os.stat() или os.lstat() приглашает условия гонки, даже если этот каталог ранее был протестирован на существование. Зачем? Поскольку внешние процессы не могут быть предотвращены одновременно с удалением этого каталога после выполнения этого теста, но до того, как этот путь будет передан в os.stat() или os.lstat() . Развяжите собак развратного безумия!

Существование существенного побочного преимущества для вышеупомянутого подхода также: безопасность. (Разве это не приятно?) В частности:

Фронтальные приложения, проверяющие произвольные пути от ненадежных источников, просто передавая такие пути к os.stat() или os.lstat() , подвержены атакам типа «отказ в обслуживании» (DoS) и другим махинациям черных шляп. Вредоносные пользователи могут пытаться повторно проверять пути, находящиеся в файловых системах, которые, как известно, являются устаревшими или в противном случае медленными (например, NFS Samba); в этом случае слепое определение входящих путей может привести к сбою или тайм-ауту соединения или потреблять больше времени и ресурсов, чем ваша слабая способность противостоять безработице.

Вышеупомянутый подход устраняет это, только проверяя компоненты пути пути к корневому каталогу корневой файловой системы. (Если даже это устаревшее, медленное или недоступное, у вас больше проблем, чем проверка имени пути).

Потерял? Отлично. Давай начнем. (Предполагается 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://msdn.microsoft.com/en-us/library/windows/desktop/ms681382%28v=vs.85%29.aspx
    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() допускает:

Примечание. Операции ввода-вывода могут завершиться неудачно, даже если os.access() указывает, что они будут успешными, особенно для операций с сетевыми файловыми системами, которые могут иметь семантику разрешений за пределами обычной модели разрешений для POSIX.

Ни для кого не удивительно, 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

Обратите внимание, однако, что этого может быть недостаточно.

Благодаря User Access Control (UAC), вездесущая Windows Vista и все последующие ее итерации вопиюще относятся к разрешениям, относящимся к системным каталогам. Когда пользователи, не являющиеся администраторами, пытаются создать файлы в канонических каталогах C:\Windows или C:\Windows\system32 , UAC поверхностно разрешает пользователю делать это, фактически изолируя все созданные файлы в «Виртуальном магазине» в профиле этого пользователя , (Кто мог бы вообразить, что обманчивые пользователи будут иметь вредные долгосрочные последствия?)

Это безумие. Это Windows.

Докажите это

Не так ли? Пришло время протестировать вышеуказанные тесты.

Поскольку 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.

https://code.i-harness.com

У меня есть путь (включая каталог и имя файла).
Мне нужно проверить, является ли имя файла действительным, например, если файловая система позволит мне создать файл с таким именем.
В имени файла есть некоторые символы Юникода .

Можно с уверенностью предположить, что сегмент каталога пути действителен и доступен ( я пытался сделать вопрос более универсальным и, видимо, слишком сильно ).

Я очень не хочу, чтобы что-то избегать, если только не придется .

Я бы опубликовал некоторые из примеров символов, с которыми я имею дело, но, видимо, они автоматически удаляются системой обмена стеками. В любом случае, я хочу сохранить стандартные юникодные объекты, такие как ö , и избегать действий, которые недействительны в имени файла.

Вот улов. Там может (или не быть) уже файл в целевой путь. Мне нужно сохранить этот файл, если он существует, и не создавать файл, если он этого не делает.

В основном я хочу проверить, могу ли я писать путь, фактически не открывая путь для записи (и автоматическое создание файлов / клонирование файлов, которое обычно влечет за собой).

В качестве таких:

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, поэтому я хотел бы избегать специфичных для платформы материалов.

``


С Python 3, как насчет:

try:
    with open(filename, 'x') as tempfile: # OSError if file exists or is invalid
        pass
except OSError:
    # handle error here

С опцией «x» нам также не нужно беспокоиться о гоночных условиях. См. Документацию here .

Теперь это будет создавать очень короткий срок временного файла, если он еще не существует - если имя недействительно. Если вы можете жить с этим, это упрощает многое.


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 может path.exists строя по нескольким причинам, чем только the file is not there поэтому вам, возможно, придется выполнять более тонкие тесты, например, проверить, существует ли содержащая директория и т. Д.

После моего обсуждения с OP выяснилось, что основная проблема заключается в том, что имя файла может содержать символы, которые не разрешены файловой системой. Конечно, их нужно удалить, но OP хочет поддерживать как можно больше информации, доступной для файловой системы.

К сожалению, я не знаю ничего хорошего для этого. Однако share более подробно рассматривает обнаружение проблемы.


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

Также посмотрите here о разрешениях на окна





filesystems