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

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

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

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

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

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


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

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

Таким образом:

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

``

4b9b3361

Ответ 1

TL;DR

Вызвать функцию is_path_exists_or_creatable(), определенную ниже.

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

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

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

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

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

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

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

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

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

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

O.K., это на самом деле. Это волосатое; это противно; он, вероятно, шутит, когда он бурлит и хихикает, когда он светится. Но что ты собираешься делать? 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 допустим тогда и только тогда, когда все компоненты пути в этом имени пути действительны. Это справедливо для большинства , представляющих интерес.

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

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

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

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

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

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

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

Забыли? Отлично.. (Предполагается Python 3. См. "Что такое хрупкая надежда для 300, ?" )

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 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: Возможно, неверное существование пути или творение, Eh?

Тестирование существования или творчества, возможно, недопустимых путей, с учетом приведенного выше решения, в основном тривиально. Маленький ключ здесь - вызвать ранее определенную функцию перед тестированием пройденного пути:

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() указывает, что они будут успешными, особенно для операций в сетевых файловых системах, которые могут иметь семантику разрешений за пределами обычной модели разрешений для 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 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 поверхностно разрешает пользователю делать это, фактически изолируя все созданные файлы в "виртуальном магазине" в этом профиле пользователя. (Кто мог бы вообразить, что обманчивые пользователи будут иметь вредные долгосрочные последствия?)

Это сумасшествие. Это 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.

Ответ 2

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

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

Ответ 3

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

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

Ответ 4

попробуйте os.path.exists, это проверит путь и вернет True, если существует, и False, если нет.