gpt4 book ai didi

python - 检查路径在 Python 中是否有效,而无需在路径的目标处创建文件

转载 作者:行者123 更新时间:2023-12-02 07:07:51 50 4
gpt4 key购买 nike

我有一个路径(包括目录和文件名)。
我需要测试文件名是否有效,例如如果文件系统允许我创建一个具有这样名称的文件。
文件名中包含一些 unicode 字符。

可以安全地假设路径的目录段是有效且可访问的(我试图使问题更适用,显然我太过分了)。

除非万不得已,否则我非常不想逃避任何事情。

我会发布一些我正在处理的示例字符,但显然它们会被堆栈交换系统自动删除。无论如何,我想保留标准的 unicode 实体,如 ö , 并且只转义文件名中无效的内容。

这是问题所在。 路径的目标处可能(或可能没有)已经有一个文件。 如果该文件确实存在,我需要保留该文件,如果不存在则不创建文件。

基本上我想检查是否可以写入路径 没有实际打开写入路径 (以及通常需要的自动文件创建/文件破坏)。

因此:

try:
open(filename, 'w')
except OSError:
# handle error here

from here

Not Acceptable ,因为它会覆盖我不想触摸的现有文件(如果它存在),或者如果它不存在则创建所述文件。

我知道我可以做到:
if not os.access(filePath, os.W_OK):
try:
open(filePath, 'w').close()
os.unlink(filePath)
except OSError:
# handle error here

但这将在 filePath 处创建文件,然后我将不得不 os.unlink .

最后,看起来像 os.isvalidpath(filePath) 那样简单的事情似乎要花 6 或 7 行代码才能完成。或类似。

顺便说一句,我需要它在(至少)Windows 和 MacOS 上运行,所以我想避免特定于平台的东西。

``

最佳答案

tl;博士

拨打 is_path_exists_or_creatable()函数定义如下。

严格来说是 Python 3。这就是我们滚动的方式。

两个问题的故事

“我如何测试路径名有效性,以及对于有效路径名,这些路径的存在或可写性”的问题?显然是两个不同的问题。两者都很有趣,而且都没有在这里得到真正令人满意的答案......或者,在我可以 grep 的任何地方。

vikkianswer可能是最接近的,但具有以下显着缺点:

  • 不必要地打开(...然后未能可靠地关闭)文件句柄。
  • 不必要地写入(...然后无法可靠地关闭或删除)0 字节文件。
  • 忽略区分不可忽略的无效路径名和可忽略的文件系统问题的特定于操作系统的错误。不出所料,这在 Windows 下很重要。 (见下文。)
  • 忽略由外部进程同时(重新)移动要测试的路径名的父目录导致的竞争条件。 (见下文。)
  • 忽略由于此路径名驻留在陈旧、缓慢或以其他方式暂时无法访问的文件系统上而导致的连接超时。这可能会将面向公众的服务暴露给潜在的 DoS驱动的攻击。 (见下文。)

  • 我们要解决这一切。

    问题 #0:什么是路径名有效性?

    在将我们脆弱的肉服扔进充满 python 的痛苦之前,我们可能应该定义“路径名有效性”的含义。究竟是什么定义了有效性?

    “路径名有效性”是指 句法正确性 相对于 的路径名根文件系统 当前系统的 - 无论该路径或其父目录是否物理存在。如果路径名符合根文件系统的所有语法要求,则在此定义下它在语法上是正确的。

    通过“根文件系统”,我们的意思是:
  • 在 POSIX 兼容系统上,文件系统挂载到根目录 ( / )。
  • 在 Windows 上,文件系统挂载到 %HOMEDRIVE% ,包含当前 Windows 安装的冒号后缀驱动器号(通常但不一定是 C: )。

  • 反过来,“语法正确性”的含义取决于根文件系统的类型。对于 ext4 (以及大多数但不是所有 POSIX 兼容的)文件系统,当且仅当该路径名在语法上是正确的:
  • 不包含空字节(即 Python 中的 \x00)。这是所有 POSIX 兼容文件系统的硬性要求。
  • 不包含长度超过 255 字节的路径组件(例如,Python 中的 'a'*256)。路径组件是路径名的最长子串,不包含 /字符(例如, bergtattindifjeldkamrene 在路径名 /bergtatt/ind/i/fjeldkamrene 中)。

  • 句法正确。根文件系统。而已。

    问题 1:我们现在应该如何进行路径名有效性?

    在 Python 中验证路径名非常不直观。我与 Fake Name 达成一致这里:官方 os.path包应该为此提供开箱即用的解决方案。由于未知(并且可能没有说服力)的原因,它没有。幸运的是,展开您自己的临时解决方案并不是那么令人痛苦……

    好吧,它实际上是。 毛茸茸的;这很糟糕;它可能会在发出咕噜声时发出咯咯声,在发光时发出咯咯声。但是你要做什么? 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当且仅当该路径名中的所有路径组件都有效时,路径名才有效。大多数情况都是如此 real-world filesystems出于兴趣。

    这种迂腐的见解真的对我们有帮助吗?是的。它将一举验证完整路径名的较大问题减少到仅验证该路径名中的所有路径组件的较小问题。任何任意路径名都可以通过遵循以下算法以跨平台方式验证(无论该路径名是否驻留在现有目录中):
  • 将该路径名拆分为路径组件(例如,路径名 /troldskog/faren/vild 到列表 ['', 'troldskog', 'faren', 'vild'] 中)。
  • 对于每个这样的组件:
  • 将保证与该组件一起存在的目录的路径名加入新的临时路径名(例如, /troldskog )。
  • 将该路径名传递给 os.stat()os.lstat() .如果该路径名以及该组件无效,则此调用保证会引发异常,从而暴露无效类型而不是通用 FileNotFoundError异常(exception)。为什么? 因为该路径名驻留在现有目录中。 (循环逻辑是循环的。)

  • 是否有保证存在的目录?是的,但通常只有一个:根文件系统的最顶层目录(如上定义)。

    将驻留在任何其他目录(因此不保证存在)中的路径名传递给 os.stat()os.lstat()邀请竞争条件,即使该目录先前已被测试存在。为什么?因为在执行该测试之后但在将该路径名传递给 os.stat() 之前,无法阻止外部进程同时删除该目录。或 os.lstat() .释放精神错乱的狗!

    上述方法也有很大的附带好处: 安全。 (这不是很好吗?)具体来说:

    Front-facing applications validating arbitrary pathnames from untrusted sources by simply passing such pathnames to os.stat() or os.lstat() are susceptible to Denial of Service (DoS) attacks and other black-hat shenanigans. Malicious users may attempt to repeatedly validate pathnames residing on filesystems known to be stale or otherwise slow (e.g., NFS Samba shares); in that case, blindly statting incoming pathnames is liable to either eventually fail with connection timeouts or consume more time and resources than your feeble capacity to withstand unemployment.



    上述方法通过仅根据根文件系统的根目录验证路径名的路径组件来避免这种情况。 (如果即使那是陈旧的、缓慢的或无法访问的,你也会遇到比路径名验证更大的问题。)

    丢了? 伟大的。 让我们开始吧。 (假设 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() documentation承认:

    Note: I/O operations may fail even when os.access() indicates that they would succeed, particularly for operations on network filesystems which may have permissions semantics beyond the usual POSIX permission-bit model.



    不出所料,Windows 是这里的常见嫌疑人。由于在 NTFS 文件系统上广泛使用访问控制列表 (ACL),简单的 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 及其所有后续迭代 blatantly lie关于与系统目录有关的权限。当非管理员用户尝试在规范 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 可移植性问题。

    关于python - 检查路径在 Python 中是否有效,而无需在路径的目标处创建文件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9532499/

    50 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com