Проверьте, является ли файловая система нечувствительной к регистру в Python
Есть ли простой способ проверить в Python, является ли файловая система нечувствительной к регистру? Я имею в виду, в частности, файловые системы, такие как HFS+ (OSX) и NTFS (Windows), где вы можете получить доступ к тому же файлу, что и foo, Foo или FOO, даже если регистр файлов сохранен.
10 ответов
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.
Ответ, предоставленный 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)
Который немного медленнее, если вызывается только один раз, и значительно быстрее в любом другом случае.
Вариант ответа @Shrikant, применимый в модуле (т.е. не в REPL), даже если у вашего пользователя нет дома:
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
И со стороны Linux:
(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, проверил pathlib
, os
, os.path
содержимое через:
[k for k in vars(pathlib).keys() if "case" in k.lower()]
и ничто не похоже на это, хотя у него есть pathlib.supports_symlinks
но ничего о чувствительности к регистру.
Хороший вопрос по различным файловым системам и т. Д. Эрик Смит. Но почему бы не использовать tempfile.NamedTeilitaryFile с параметром dir и избегать того, чтобы весь этот контекстный менеджер поднимал себя?
def is_fs_case_sensitive(path):
#
# Force case with the prefix
#
with tempfile.NamedTemporaryFile(prefix='TmP',dir=path) as tmp_file:
return(not os.path.exists(tmp_file.name.lower()))
Я должен также упомянуть, что ваше решение не гарантирует, что вы действительно проверяете чувствительность к регистру. Если вы не проверите префикс по умолчанию (используя tempfile.gettempprefix()), чтобы убедиться, что он содержит символ нижнего регистра. Поэтому включение префикса здесь не является обязательным.
Ваше решение очищает временный файл. Я согласен, что это казалось очевидным, но никто не знает, не так ли?
Я думаю, что есть гораздо более простое (и, вероятно, более быстрое) решение этой проблемы. Следующее, казалось, работало там, где я тестировал:
import os.path
home = os.path.expanduser('~')
is_fs_case_insensitive = os.path.exists(home.upper()) and os.path.exists(home.lower())
Начиная с ответа Амбер, я придумал этот код. Я не уверен, что он полностью устойчив, но он пытается решить некоторые проблемы в оригинале (о которых я расскажу ниже).
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)))
Без указания dir
параметр в mkstemp
вопрос чувствительности к регистру неясен. Вы проверяете чувствительность к регистру, где бы ни находился временный каталог, но вы, возможно, захотите узнать о конкретном пути.
Если вы преобразуете полный путь, возвращенный из mkstemp
в верхнем регистре, вы можете пропустить переход где-нибудь на пути. Например, у меня есть флешка в Linux, смонтированная с помощью vfat на /media/FLASH
, Проверка существования чего-либо под /MEDIA/FLASH
всегда потерпит неудачу, потому что /media
находится на (чувствительном к регистру) разделе ext4, но сама флешка нечувствительна к регистру. Установленные сетевые ресурсы могут быть другой ситуацией, подобной этой.
Наконец, и, возможно, само собой разумеется, в ответе Амбер вы захотите очистить временный файл, созданный mk stemp.
import os
if os.path.normcase('A') == os.path.normcase('a'):
# case insensitive
else:
# case sensitive
Проверка существования варианта пути в верхнем/строчном регистре ошибочна. На момент написания этой статьи существовало семь ответов, основанных на одной и той же стратегии: начать с пути (временный файл, домашний каталог или сам файл Python), а затем проверить наличие варианта этого пути с измененным регистром. . Даже если оставить в стороне проблему настройки чувствительности к регистру для каждого каталога, этот подход в корне неверен.
Почему этот подход не работает в файловых системах, чувствительных к регистру. Рассмотрим подход к использованию временных файлов. Когда библиотека возвращает временный файл, единственной гарантией является то, что на момент создания путь не существовал – и все. Если часть имени файла этого пути
Лучший подход: начните с каталога, которым вы управляете. Более надежный подход — начать с временного каталога, который гарантированно будет пуст в момент создания. В этом каталоге вы можете выполнить концептуально обоснованные тесты на чувствительность к регистру.
Лучший подход: различать регистронезависимые и регистро-сохраняющие. Для проекта, над которым я работаю, мне нужно провести это различие (и я могу игнорировать настройки чувствительности к регистру для каждого каталога), поэтому в итоге я получил следующее.
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'
)
Я думаю, мы можем сделать это заодно с
pathlib
на Python 3.5+ без создания временных файлов:
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()
Я считаю, что это самое простое решение вопроса:
from fnmatch import fnmatch
os_is_case_insensitive = fnmatch('A','a')
От: https://docs.python.org/3.4/library/fnmatch.html
Если операционная система нечувствительна к регистру, то перед выполнением сравнения оба параметра будут нормализованы для всех строчных и прописных букв.