检查Python中的文件系统是否不区分大小写

发布于 2024-12-11 15:31:35 字数 126 浏览 1 评论 0原文

有没有一种简单的方法可以在Python中检查文件系统是否不区分大小写?我特别想到像 HFS+ (OSX) 和 NTFS (Windows) 这样的文件系统,您可以在其中访问与 foo、Foo 或 FOO 相同的文件,即使文件大小写被保留。

Is there a simple way to check in Python if a file system is case insensitive? I'm thinking in particular of file systems like HFS+ (OSX) and NTFS (Windows), where you can access the same file as foo, Foo or FOO, even though the file case is preserved.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(10

青衫儰鉨ミ守葔 2024-12-18 15:31:35
import os
import tempfile

# By default mkstemp() creates a file with
# a name that begins with 'tmp' (lowercase)
tmphandle, tmppath = tempfile.mkstemp()
if os.path.exists(tmppath.upper()):
    # Case insensitive.
else:
    # Case sensitive.
import os
import tempfile

# By default mkstemp() creates a file with
# a name that begins with 'tmp' (lowercase)
tmphandle, tmppath = tempfile.mkstemp()
if os.path.exists(tmppath.upper()):
    # Case insensitive.
else:
    # Case sensitive.
人生百味 2024-12-18 15:31:35

Amber 提供的答案将留下临时文件碎片,除非明确处理关闭和删除。为了避免这种情况,我使用:

import os
import tempfile

def is_fs_case_sensitive():
    #
    # Force case with the prefix
    #
    with tempfile.NamedTemporaryFile(prefix='TmP') as tmp_file:
        return(not os.path.exists(tmp_file.name.lower()))

虽然我的使用案例通常会多次测试这一点,所以我隐藏结果以避免多次接触文件系统。

def is_fs_case_sensitive():
    if not hasattr(is_fs_case_sensitive, 'case_sensitive'):
        with tempfile.NamedTemporaryFile(prefix='TmP') as tmp_file:
            setattr(is_fs_case_sensitive,
                    'case_sensitive',
                    not os.path.exists(tmp_file.name.lower()))
    return(is_fs_case_sensitive.case_sensitive)

如果只调用一次,速度会稍微慢一些,而在其他情况下速度会明显加快。

The answer provided by Amber will leave temporary file debris unless closing and deleting are handled explicitly. To avoid this I use:

import os
import tempfile

def is_fs_case_sensitive():
    #
    # Force case with the prefix
    #
    with tempfile.NamedTemporaryFile(prefix='TmP') as tmp_file:
        return(not os.path.exists(tmp_file.name.lower()))

Though my usage cases generally test this more than once, so I stash the result to avoid having to touch the filesystem more than once.

def is_fs_case_sensitive():
    if not hasattr(is_fs_case_sensitive, 'case_sensitive'):
        with tempfile.NamedTemporaryFile(prefix='TmP') as tmp_file:
            setattr(is_fs_case_sensitive,
                    'case_sensitive',
                    not os.path.exists(tmp_file.name.lower()))
    return(is_fs_case_sensitive.case_sensitive)

Which is marginally slower if only called once, and significantly faster in every other case.

秋千易 2024-12-18 15:31:35

关于不同文件系统等的好点,Eric Smith。但为什么不将 tempfile.NamedTemporaryFile 与 dir 参数一起使用,并避免自己执行所有上下文管理器操作呢?

def is_fs_case_sensitive(path):
    #
    # Force case with the prefix
    #
    with tempfile.NamedTemporaryFile(prefix='TmP',dir=path, delete=True) as tmp_file:
        return(not os.path.exists(tmp_file.name.lower()))

我还应该提到,您的解决方案并不能保证您实际上正在测试区分大小写。除非您检查默认前缀(使用 tempfile.gettempprefix())以确保它包含小写字符。因此,在这里包含前缀并不是真正可选的。

您的解决方案会清理临时文件。我同意这似乎是显而易见的,但人们永远不知道,不是吗?

Good point on the different file systems, etc., Eric Smith. But why not use tempfile.NamedTemporaryFile with the dir parameter and avoid doing all that context manager lifting yourself?

def is_fs_case_sensitive(path):
    #
    # Force case with the prefix
    #
    with tempfile.NamedTemporaryFile(prefix='TmP',dir=path, delete=True) as tmp_file:
        return(not os.path.exists(tmp_file.name.lower()))

I should also mention that your solution does not guarantee that you are actually testing for case sensitivity. Unless you check the default prefix (using tempfile.gettempprefix()) to make sure it contains a lower-case character. So including the prefix here is not really optional.

Your solution cleans up the temp file. I agree that it seemed obvious, but one never knows, do one?

初相遇 2024-12-18 15:31:35

@Shrikant 答案的变体,适用于模块内(即不在 REPL 中),即使您的用户没有 home:

import os.path
is_fs_case_insensitive = os.path.exists(__file__.upper()) and os.path.exists(__file__.lower())
print(f"{is_fs_case_insensitive=}")

输出(macOS):

is_fs_case_insensitive=True 

Variation on @Shrikant's answer, applicable within a module (i.e. not in the REPL), even if your user doesn't have a home:

import os.path
is_fs_case_insensitive = os.path.exists(__file__.upper()) and os.path.exists(__file__.lower())
print(f"{is_fs_case_insensitive=}")

output (macOS):

is_fs_case_insensitive=True ????

And the Linux side of things:

(ssha)vagrant ~$python3.8 test.py
is_fs_case_insensitive=False ????
(ssha)vagrant ~$lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 20.04 LTS
Release:    20.04
Codename:   focal

FWIW, I checked pathlib, os, os.path's contents via:

[k for k in vars(pathlib).keys() if "case" in k.lower()]

and nothing looks like it, though it does have a pathlib.supports_symlinks but nothing about case-sensitivity.

And the following will work in the REPL as well:

is_fs_case_insensitive = os.path.exists(os.path.__file__.upper()) and os.path.exists(os.path.__file__.lower())
呢古 2024-12-18 15:31:35

我认为有一个更简单(而且可能更快)的解决方案。以下似乎适用于我测试的地方:

import os.path
home = os.path.expanduser('~')
is_fs_case_insensitive = os.path.exists(home.upper()) and os.path.exists(home.lower())

I think there's a much simpler (and probably faster) solution to this. The following seemed to be working for where I tested:

import os.path
home = os.path.expanduser('~')
is_fs_case_insensitive = os.path.exists(home.upper()) and os.path.exists(home.lower())
苹果你个爱泡泡 2024-12-18 15:31:35
import os

if os.path.normcase('A') == os.path.normcase('a'):
    # case insensitive
else:
    # case sensitive
import os

if os.path.normcase('A') == os.path.normcase('a'):
    # case insensitive
else:
    # case sensitive
晨曦÷微暖 2024-12-18 15:31:35

从 Amber 的回答开始,我想出了这段代码。我不确定它是否完全可靠,但它试图解决原始版本中的一些问题(我将在下面提到)。

import os
import sys
import tempfile
import contextlib


def is_case_sensitive(path):
    with temp(path) as tmppath:
        head, tail = os.path.split(tmppath)
        testpath = os.path.join(head, tail.upper())
        return not os.path.exists(testpath)


@contextlib.contextmanager
def temp(path):
    tmphandle, tmppath = tempfile.mkstemp(dir=path)
    os.close(tmphandle)
    try:
        yield tmppath
    finally:
        os.unlink(tmppath)


if __name__ == '__main__':
    path = os.path.abspath(sys.argv[1])
    print(path)
    print('Case sensitive: ' + str(is_case_sensitive(path)))

如果没有在 mkstemp 中指定 dir 参数,则区分大小写的问题是模糊的。您正在测试临时目录所在位置的区分大小写,但您可能想了解特定路径。

如果将从 mkstemp 返回的完整路径转换为大写,则可能会错过路径中某处的转换。例如,我在 Linux 上使用 vfat 在 /media/FLASH 挂载了一个 USB 闪存驱动器。测试 /MEDIA/FLASH 下是否存在任何内容总是会失败,因为 /media 位于(区分大小写的)ext4 分区上,但闪存驱动器本身不区分大小写。安装的网络共享可能是另一种类似的情况。

最后,也许在 Amber 的回答中不言而喻,您需要清理由 mkstemp 创建的临时文件。

Starting with Amber's answer, I came up with this code. I'm not sure it is totally robust, but it attempts to address some issues in the original (that I'll mention below).

import os
import sys
import tempfile
import contextlib


def is_case_sensitive(path):
    with temp(path) as tmppath:
        head, tail = os.path.split(tmppath)
        testpath = os.path.join(head, tail.upper())
        return not os.path.exists(testpath)


@contextlib.contextmanager
def temp(path):
    tmphandle, tmppath = tempfile.mkstemp(dir=path)
    os.close(tmphandle)
    try:
        yield tmppath
    finally:
        os.unlink(tmppath)


if __name__ == '__main__':
    path = os.path.abspath(sys.argv[1])
    print(path)
    print('Case sensitive: ' + str(is_case_sensitive(path)))

Without specifying the dir parameter in mkstemp, the question of case sensitivity is vague. You're testing case sensitivity of wherever the temporary directory happens to be, but you may want to know about a specific path.

If you convert the full path returned from mkstemp to upper-case, you could potentially miss a transition somewhere in the path. For example, I have a USB flash drive on Linux mounted using vfat at /media/FLASH. Testing the existence of anything under /MEDIA/FLASH will always fail because /media is on a (case-sensitive) ext4 partition, but the flash drive itself is case-insensitive. Mounted network shares could be another situation like this.

Finally, and maybe it goes without saying in Amber's answer, you'll want to clean up the temp file created by mkstemp.

愿与i 2024-12-18 15:31:35

我认为我们可以在 Python 3.5+ 上使用 pathlib 一行完成此操作,而无需创建临时文件:

from pathlib import Path

def is_case_insensitive(path) -> bool:
    return Path(str(Path.home()).upper()).exists()

或者相反:

def is_case_sensitive(path) -> bool:
    return not Path(str(Path.home()).upper()).exists()

I think we can do this in one line with pathlib on Python 3.5+ without creating temporary files:

from pathlib import Path

def is_case_insensitive(path) -> bool:
    return Path(str(Path.home()).upper()).exists()

Or for the inverse:

def is_case_sensitive(path) -> bool:
    return not Path(str(Path.home()).upper()).exists()
你与清晨阳光 2024-12-18 15:31:35

检查路径是否存在大写/小写变体
有缺陷
。在撰写本文时,有七个答案依赖于
相同的策略:以路径(临时文件、主目录或 Python
文件本身),然后检查该文件是否存在大小写更改的变体
小路。甚至抛开每个目录区分大小写的问题
配置,这种做法根本上是无效的。

为什么该方法在区分大小写的文件系统上失败。考虑温度
文件方法。当 tempfile 库返回临时文件时,唯一的
保证在创建之前的那一刻,路径没有
存在——仅此而已。如果文件名
该路径的一部分是 FoO,我们对 的存在状态一无所知
fooFOO 或任何其他大小写变体。诚然,tempfile 库倾向于
返回诸如 TmP5pq3us96 这样的名字,其邪恶的可能性非常低
案例改变的双胞胎存在——但我们不知道。同样的缺陷影响
使用主目录或 Python 文件的方法:很可能,
/HOME/FOO/FOO/BAR/FUBB.PY 不存在...但我们没有理由这样做
肯定地假设。

更好的方法:从您控制的目录开始。更坚固的
方法是从临时目录开始,保证该目录为空
创造的时刻。在该目录中,您可以从概念上执行
区分大小写的声音测试。

更好的方法:区分不区分大小写和
保留大小写
。对于我正在从事的项目,我需要做到这一点
区别(我可以忽略每个目录的区分大小写设置),所以我
最终得到以下结果。

from functools import cache
from pathlib import Path
from tempfile import TemporaryDirectory

@cache
def file_system_case_sensitivity():
    # Determines the file system's case sensitivity.
    # This approach ignore the complexity of per-directory
    # sensitivity settings supported by some operating systems.
    with TemporaryDirectory() as dpath:
        # Create an empty temp directory.
        # Inside it, touch two differently-cased file names.
        d = Path(dpath)
        f1 = d / 'FoO'
        f2 = d / 'foo'
        f1.touch()
        f2.touch()
        # Ask the file system to report the contents of the temp directory.
        # - If two files, system is case-sensitive.
        # - If the parent reports having 'FoO', case-preserving.
        # - Case-insensitive systems will report having 'foo' or 'FOO'.
        contents = tuple(d.iterdir())
        return (
            'case-sensitive' if len(contents) == 2 else
            'case-preserving' if contents == (f1,) else
            'case-insensitive'
        )

Checking for the existence of an uppercase/lowercase variant of a path
is flawed
. At the time of this writing, there are seven answers that rely on
the same strategy: start with a path (temp file, home directory, or the Python
file itself) and then check for the existence of a case-altered variant of that
path. Even setting aside the issue of per-directory case-sensitivity
configuration, that approach is fundamentally invalid.

Why the approach fails on case-sensitive file systems. Consider the temp
file approach. When the tempfile library returns a temp file, the only
guarantee is that at the instant before creation, the path did not
exist – that's it. If the file-name
portion of that path is FoO, we know nothing about the existence status of
foo, FOO, or any other case-variant. Granted, the tempfile library tends
to return names like TmP5pq3us96 and the odds are very low that its evil
case-altered twin exists – but we don't know that. The same flaw affects the
approaches using the home directory or the Python file: in all likelihood,
/HOME/FOO or /FOO/BAR/FUBB.PY do not exist ... but we have no reason to
assume that with certainty.

A better approach: start with a directory that you control. A more robust
approach is to begin with a temp directory, which is guaranteed to be empty at
the moment of creation. Within that directory, you can perform conceptually
sound tests for case sensitivity.

A better approach: distinguish between case-insensitive and
case-preserving
. For a project I'm working on, I need to make that
distinction (and I can ignore per-directory case-sensitivity settings), so I
ended up with the following.

from functools import cache
from pathlib import Path
from tempfile import TemporaryDirectory

@cache
def file_system_case_sensitivity():
    # Determines the file system's case sensitivity.
    # This approach ignore the complexity of per-directory
    # sensitivity settings supported by some operating systems.
    with TemporaryDirectory() as dpath:
        # Create an empty temp directory.
        # Inside it, touch two differently-cased file names.
        d = Path(dpath)
        f1 = d / 'FoO'
        f2 = d / 'foo'
        f1.touch()
        f2.touch()
        # Ask the file system to report the contents of the temp directory.
        # - If two files, system is case-sensitive.
        # - If the parent reports having 'FoO', case-preserving.
        # - Case-insensitive systems will report having 'foo' or 'FOO'.
        contents = tuple(d.iterdir())
        return (
            'case-sensitive' if len(contents) == 2 else
            'case-preserving' if contents == (f1,) else
            'case-insensitive'
        )
别闹i 2024-12-18 15:31:35

我相信这是这个问题最简单的解决方案:

from fnmatch import fnmatch
os_is_case_insensitive = fnmatch('A','a')

来自: https://docs.python.org/3.4/library/fnmatch.html< /a>

如果操作系统不区分大小写,则这两个参数都将
在比较之前将其标准化为所有小写或大写
已执行。

I believe this to be the simplest solution to the question:

from fnmatch import fnmatch
os_is_case_insensitive = fnmatch('A','a')

From: https://docs.python.org/3.4/library/fnmatch.html

If the operating system is case-insensitive, then both parameters will
be normalized to all lower- or upper-case before the comparison is
performed.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文